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