Coverage Report

Created: 2025-06-13 06:55

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