Coverage Report

Created: 2025-07-23 08:13

/src/pango/subprojects/glib/gio/inotify/inotify-helper.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-helper.c - GVFS Monitor based on inotify.
4
5
   Copyright (C) 2007 John McCutchan
6
7
   SPDX-License-Identifier: LGPL-2.1-or-later
8
9
   This library is free software; you can redistribute it and/or
10
   modify it under the terms of the GNU Lesser General Public
11
   License as published by the Free Software Foundation; either
12
   version 2.1 of the License, or (at your option) any later version.
13
14
   This library is distributed in the hope that it will be useful,
15
   but WITHOUT ANY WARRANTY; without even the implied warranty of
16
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17
   Lesser General Public License for more details.
18
19
   You should have received a copy of the GNU Lesser General Public License
20
   along with this library; if not, see <http://www.gnu.org/licenses/>.
21
22
   Authors: 
23
     John McCutchan <john@johnmccutchan.com>
24
*/
25
26
#include "config.h"
27
#include <errno.h>
28
#include <time.h>
29
#include <string.h>
30
#include <sys/ioctl.h>
31
#include <sys/stat.h>
32
/* Just include the local header to stop all the pain */
33
#include <sys/inotify.h>
34
#include <gio/glocalfilemonitor.h>
35
#include <gio/gfile.h>
36
#include "inotify-helper.h"
37
#include "inotify-missing.h"
38
#include "inotify-path.h"
39
40
static gboolean ih_debug_enabled = FALSE;
41
0
#define IH_W if (ih_debug_enabled) g_warning 
42
43
static gboolean ih_event_callback (ik_event_t  *event,
44
                                   inotify_sub *sub,
45
                                   gboolean     file_event);
46
static void ih_not_missing_callback (inotify_sub *sub);
47
48
/* We share this lock with inotify-kernel.c and inotify-missing.c
49
 *
50
 * inotify-kernel.c takes the lock when it reads events from
51
 * the kernel and when it processes those events
52
 *
53
 * inotify-missing.c takes the lock when it is scanning the missing
54
 * list.
55
 *
56
 * We take the lock in all public functions
57
 */
58
G_LOCK_DEFINE (inotify_lock);
59
60
static GFileMonitorEvent ih_mask_to_EventFlags (guint32 mask);
61
62
/**
63
 * _ih_startup:
64
 *
65
 * Initializes the inotify backend.  This must be called before
66
 * any other functions in this module.
67
 *
68
 * Returns: #TRUE if initialization succeeded, #FALSE otherwise
69
 */
70
gboolean
71
_ih_startup (void)
72
0
{
73
0
  static gboolean initialized = FALSE;
74
0
  static gboolean result = FALSE;
75
  
76
0
  G_LOCK (inotify_lock);
77
  
78
0
  if (initialized == TRUE)
79
0
    {
80
0
      G_UNLOCK (inotify_lock);
81
0
      return result;
82
0
    }
83
84
0
  result = _ip_startup (ih_event_callback);
85
0
  if (!result)
86
0
    {
87
0
      G_UNLOCK (inotify_lock);
88
0
      return FALSE;
89
0
    }
90
0
  _im_startup (ih_not_missing_callback);
91
92
0
  IH_W ("started gvfs inotify backend\n");
93
  
94
0
  initialized = TRUE;
95
  
96
0
  G_UNLOCK (inotify_lock);
97
  
98
0
  return TRUE;
99
0
}
100
101
/*
102
 * Adds a subscription to be monitored.
103
 */
104
gboolean
105
_ih_sub_add (inotify_sub *sub)
106
0
{
107
0
  G_LOCK (inotify_lock);
108
  
109
0
  if (!_ip_start_watching (sub))
110
0
    _im_add (sub);
111
  
112
0
  G_UNLOCK (inotify_lock);
113
114
0
  return TRUE;
115
0
}
116
117
/*
118
 * Cancels a subscription which was being monitored.
119
 */
120
gboolean
121
_ih_sub_cancel (inotify_sub *sub)
122
0
{
123
0
  G_LOCK (inotify_lock);
124
125
0
  if (!sub->cancelled)
126
0
    {
127
0
      IH_W ("cancelling %s\n", sub->dirname);
128
0
      sub->cancelled = TRUE;
129
0
      _im_rm (sub);
130
0
      _ip_stop_watching (sub);
131
0
    }
132
  
133
0
  G_UNLOCK (inotify_lock);
134
135
0
  return TRUE;
136
0
}
137
138
static char *
139
_ih_fullpath_from_event (ik_event_t *event,
140
       const char *dirname,
141
       const char *filename)
142
0
{
143
0
  char *fullpath;
144
145
0
  if (filename)
146
0
    fullpath = g_strdup_printf ("%s/%s", dirname, filename);
147
0
  else if (event->name)
148
0
    fullpath = g_strdup_printf ("%s/%s", dirname, event->name);
149
0
  else
150
0
    fullpath = g_strdup_printf ("%s/", dirname);
151
152
0
   return fullpath;
153
0
}
154
155
static gboolean
156
ih_event_callback (ik_event_t  *event,
157
                   inotify_sub *sub,
158
                   gboolean     file_event)
159
0
{
160
0
  gboolean interesting;
161
0
  GFileMonitorEvent event_flags;
162
163
0
  event_flags = ih_mask_to_EventFlags (event->mask);
164
165
0
  if (event->mask & IN_MOVE)
166
0
    {
167
      /* We either have a rename (in the same directory) or a move
168
       * (between different directories).
169
       */
170
0
      if (event->pair && event->pair->wd == event->wd)
171
0
        {
172
          /* this is a rename */
173
0
          interesting = g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_RENAMED,
174
0
                                                            event->name, event->pair->name, NULL, event->timestamp);
175
0
        }
176
0
      else
177
0
        {
178
0
          GFile *other;
179
180
0
          if (event->pair)
181
0
            {
182
0
              const char *parent_dir;
183
0
              gchar *fullpath;
184
185
0
              parent_dir = _ip_get_path_for_wd (event->pair->wd);
186
0
              fullpath = _ih_fullpath_from_event (event->pair, parent_dir, NULL);
187
0
              other = g_file_new_for_path (fullpath);
188
0
              g_free (fullpath);
189
0
            }
190
0
          else
191
0
            other = NULL;
192
193
          /* This is either an incoming or outgoing move. Since we checked the
194
           * event->mask above, it should have converted to a #GFileMonitorEvent
195
           * properly. If not, the assumption we have made about event->mask
196
           * only ever having a single bit set (apart from IN_ISDIR) is false.
197
           * The kernel documentation is lacking here. */
198
0
          g_assert ((int) event_flags != -1);
199
0
          interesting = g_file_monitor_source_handle_event (sub->user_data, event_flags,
200
0
                                                            event->name, NULL, other, event->timestamp);
201
202
0
          if (other)
203
0
            g_object_unref (other);
204
0
        }
205
0
    }
206
0
  else if ((int) event_flags != -1)
207
    /* unpaired event -- no 'other' field */
208
0
    interesting = g_file_monitor_source_handle_event (sub->user_data, event_flags,
209
0
                                                      event->name, NULL, NULL, event->timestamp);
210
0
  else
211
0
    interesting = FALSE;
212
213
0
  if (event->mask & IN_CREATE)
214
0
    {
215
0
      const gchar *parent_dir;
216
0
      gchar *fullname;
217
0
      struct stat buf;
218
0
      gint s;
219
220
      /* The kernel reports IN_CREATE for two types of events:
221
       *
222
       *  - creat(), in which case IN_CLOSE_WRITE will come soon; or
223
       *  - link(), mkdir(), mknod(), etc., in which case it won't
224
       *
225
       * We can attempt to detect the second case and send the
226
       * CHANGES_DONE immediately so that the user isn't left waiting.
227
       *
228
       * The detection for link() is not 100% reliable since the link
229
       * count could be 1 if the original link was deleted or if
230
       * O_TMPFILE was being used, but in that case the virtual
231
       * CHANGES_DONE will be emitted to close the loop.
232
       */
233
234
0
      parent_dir = _ip_get_path_for_wd (event->wd);
235
0
      fullname = _ih_fullpath_from_event (event, parent_dir, NULL);
236
0
      s = stat (fullname, &buf);
237
0
      g_free (fullname);
238
239
      /* if it doesn't look like the result of creat()... */
240
0
      if (s != 0 || !S_ISREG (buf.st_mode) || buf.st_nlink != 1)
241
0
        g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
242
0
                                            event->name, NULL, NULL, event->timestamp);
243
0
    }
244
245
0
  return interesting;
246
0
}
247
248
static void
249
ih_not_missing_callback (inotify_sub *sub)
250
0
{
251
0
  gint now = g_get_monotonic_time ();
252
253
0
  g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_CREATED,
254
0
                                      sub->filename, NULL, NULL, now);
255
0
  g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
256
0
                                      sub->filename, NULL, NULL, now);
257
0
}
258
259
/* Transforms a inotify event to a GVFS event. */
260
static GFileMonitorEvent
261
ih_mask_to_EventFlags (guint32 mask)
262
0
{
263
0
  mask &= ~IN_ISDIR;
264
0
  switch (mask)
265
0
    {
266
0
    case IN_MODIFY:
267
0
      return G_FILE_MONITOR_EVENT_CHANGED;
268
0
    case IN_CLOSE_WRITE:
269
0
      return G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT;
270
0
    case IN_ATTRIB:
271
0
      return G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED;
272
0
    case IN_MOVE_SELF:
273
0
    case IN_DELETE:
274
0
    case IN_DELETE_SELF:
275
0
      return G_FILE_MONITOR_EVENT_DELETED;
276
0
    case IN_CREATE:
277
0
      return G_FILE_MONITOR_EVENT_CREATED;
278
0
    case IN_MOVED_FROM:
279
0
      return G_FILE_MONITOR_EVENT_MOVED_OUT;
280
0
    case IN_MOVED_TO:
281
0
      return G_FILE_MONITOR_EVENT_MOVED_IN;
282
0
    case IN_UNMOUNT:
283
0
      return G_FILE_MONITOR_EVENT_UNMOUNTED;
284
0
    case IN_Q_OVERFLOW:
285
0
    case IN_OPEN:
286
0
    case IN_CLOSE_NOWRITE:
287
0
    case IN_ACCESS:
288
0
    case IN_IGNORED:
289
0
    default:
290
0
      return -1;
291
0
    }
292
0
}