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
38static gboolean ih_debug_enabled = FALSE;
39#define IH_W if (ih_debug_enabled) g_warning
40
41static gboolean ih_event_callback (ik_event_t *event,
42 inotify_sub *sub,
43 gboolean file_event);
44static 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 */
56G_LOCK_DEFINE (inotify_lock);
57
58static 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 */
68gboolean
69_ih_startup (void)
70{
71 static gboolean initialized = FALSE;
72 static gboolean result = FALSE;
73
74 G_LOCK (inotify_lock);
75
76 if (initialized == TRUE)
77 {
78 G_UNLOCK (inotify_lock);
79 return result;
80 }
81
82 result = _ip_startup (event_cb: ih_event_callback);
83 if (!result)
84 {
85 G_UNLOCK (inotify_lock);
86 return FALSE;
87 }
88 _im_startup (missing_cb: ih_not_missing_callback);
89
90 IH_W ("started gvfs inotify backend\n");
91
92 initialized = TRUE;
93
94 G_UNLOCK (inotify_lock);
95
96 return TRUE;
97}
98
99/*
100 * Adds a subscription to be monitored.
101 */
102gboolean
103_ih_sub_add (inotify_sub *sub)
104{
105 G_LOCK (inotify_lock);
106
107 if (!_ip_start_watching (sub))
108 _im_add (sub);
109
110 G_UNLOCK (inotify_lock);
111
112 return TRUE;
113}
114
115/*
116 * Cancels a subscription which was being monitored.
117 */
118gboolean
119_ih_sub_cancel (inotify_sub *sub)
120{
121 G_LOCK (inotify_lock);
122
123 if (!sub->cancelled)
124 {
125 IH_W ("cancelling %s\n", sub->dirname);
126 sub->cancelled = TRUE;
127 _im_rm (sub);
128 _ip_stop_watching (sub);
129 }
130
131 G_UNLOCK (inotify_lock);
132
133 return TRUE;
134}
135
136static char *
137_ih_fullpath_from_event (ik_event_t *event,
138 const char *dirname,
139 const char *filename)
140{
141 char *fullpath;
142
143 if (filename)
144 fullpath = g_strdup_printf (format: "%s/%s", dirname, filename);
145 else if (event->name)
146 fullpath = g_strdup_printf (format: "%s/%s", dirname, event->name);
147 else
148 fullpath = g_strdup_printf (format: "%s/", dirname);
149
150 return fullpath;
151}
152
153static gboolean
154ih_event_callback (ik_event_t *event,
155 inotify_sub *sub,
156 gboolean file_event)
157{
158 gboolean interesting;
159 GFileMonitorEvent event_flags;
160
161 event_flags = ih_mask_to_EventFlags (mask: event->mask);
162
163 if (event->mask & IN_MOVE)
164 {
165 /* We either have a rename (in the same directory) or a move
166 * (between different directories).
167 */
168 if (event->pair && event->pair->wd == event->wd)
169 {
170 /* this is a rename */
171 interesting = g_file_monitor_source_handle_event (fms: sub->user_data, event_type: G_FILE_MONITOR_EVENT_RENAMED,
172 child: event->name, rename_to: event->pair->name, NULL, event_time: event->timestamp);
173 }
174 else
175 {
176 GFile *other;
177
178 if (event->pair)
179 {
180 const char *parent_dir;
181 gchar *fullpath;
182
183 parent_dir = _ip_get_path_for_wd (wd: event->pair->wd);
184 fullpath = _ih_fullpath_from_event (event: event->pair, dirname: parent_dir, NULL);
185 other = g_file_new_for_path (path: fullpath);
186 g_free (mem: fullpath);
187 }
188 else
189 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 g_assert ((int) event_flags != -1);
197 interesting = g_file_monitor_source_handle_event (fms: sub->user_data, event_type: event_flags,
198 child: event->name, NULL, other, event_time: event->timestamp);
199
200 if (other)
201 g_object_unref (object: other);
202 }
203 }
204 else if ((int) event_flags != -1)
205 /* unpaired event -- no 'other' field */
206 interesting = g_file_monitor_source_handle_event (fms: sub->user_data, event_type: event_flags,
207 child: event->name, NULL, NULL, event_time: event->timestamp);
208 else
209 interesting = FALSE;
210
211 if (event->mask & IN_CREATE)
212 {
213 const gchar *parent_dir;
214 gchar *fullname;
215 struct stat buf;
216 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 parent_dir = _ip_get_path_for_wd (wd: event->wd);
233 fullname = _ih_fullpath_from_event (event, dirname: parent_dir, NULL);
234 s = stat (file: fullname, buf: &buf);
235 g_free (mem: fullname);
236
237 /* if it doesn't look like the result of creat()... */
238 if (s != 0 || !S_ISREG (buf.st_mode) || buf.st_nlink != 1)
239 g_file_monitor_source_handle_event (fms: sub->user_data, event_type: G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
240 child: event->name, NULL, NULL, event_time: event->timestamp);
241 }
242
243 return interesting;
244}
245
246static void
247ih_not_missing_callback (inotify_sub *sub)
248{
249 gint now = g_get_monotonic_time ();
250
251 g_file_monitor_source_handle_event (fms: sub->user_data, event_type: G_FILE_MONITOR_EVENT_CREATED,
252 child: sub->filename, NULL, NULL, event_time: now);
253 g_file_monitor_source_handle_event (fms: sub->user_data, event_type: G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
254 child: sub->filename, NULL, NULL, event_time: now);
255}
256
257/* Transforms a inotify event to a GVFS event. */
258static GFileMonitorEvent
259ih_mask_to_EventFlags (guint32 mask)
260{
261 mask &= ~IN_ISDIR;
262 switch (mask)
263 {
264 case IN_MODIFY:
265 return G_FILE_MONITOR_EVENT_CHANGED;
266 case IN_CLOSE_WRITE:
267 return G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT;
268 case IN_ATTRIB:
269 return G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED;
270 case IN_MOVE_SELF:
271 case IN_DELETE:
272 case IN_DELETE_SELF:
273 return G_FILE_MONITOR_EVENT_DELETED;
274 case IN_CREATE:
275 return G_FILE_MONITOR_EVENT_CREATED;
276 case IN_MOVED_FROM:
277 return G_FILE_MONITOR_EVENT_MOVED_OUT;
278 case IN_MOVED_TO:
279 return G_FILE_MONITOR_EVENT_MOVED_IN;
280 case IN_UNMOUNT:
281 return G_FILE_MONITOR_EVENT_UNMOUNTED;
282 case IN_Q_OVERFLOW:
283 case IN_OPEN:
284 case IN_CLOSE_NOWRITE:
285 case IN_ACCESS:
286 case IN_IGNORED:
287 default:
288 return -1;
289 }
290}
291

source code of gtk/subprojects/glib/gio/inotify/inotify-helper.c