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