• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
_ih_startup(void)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 (ih_event_callback);
83   if (!result)
84     {
85       G_UNLOCK (inotify_lock);
86       return FALSE;
87     }
88   _im_startup (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  */
102 gboolean
_ih_sub_add(inotify_sub * sub)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  */
118 gboolean
_ih_sub_cancel(inotify_sub * sub)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 
136 static char *
_ih_fullpath_from_event(ik_event_t * event,const char * dirname,const char * filename)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 ("%s/%s", dirname, filename);
145   else if (event->name)
146     fullpath = g_strdup_printf ("%s/%s", dirname, event->name);
147   else
148     fullpath = g_strdup_printf ("%s/", dirname);
149 
150    return fullpath;
151 }
152 
153 static gboolean
ih_event_callback(ik_event_t * event,inotify_sub * sub,gboolean file_event)154 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   event_flags = ih_mask_to_EventFlags (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 (sub->user_data, G_FILE_MONITOR_EVENT_RENAMED,
172                                                             event->name, event->pair->name, NULL, 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 (event->pair->wd);
184               fullpath = _ih_fullpath_from_event (event->pair, parent_dir, NULL);
185               other = g_file_new_for_path (fullpath);
186               g_free (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 (sub->user_data, event_flags,
198                                                             event->name, NULL, other, event->timestamp);
199 
200           if (other)
201             g_object_unref (other);
202         }
203     }
204   else if ((int) event_flags != -1)
205     /* unpaired event -- no 'other' field */
206     interesting = g_file_monitor_source_handle_event (sub->user_data, event_flags,
207                                                       event->name, NULL, NULL, 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 (event->wd);
233       fullname = _ih_fullpath_from_event (event, parent_dir, NULL);
234       s = stat (fullname, &buf);
235       g_free (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 (sub->user_data, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
240                                             event->name, NULL, NULL, event->timestamp);
241     }
242 
243   return interesting;
244 }
245 
246 static void
ih_not_missing_callback(inotify_sub * sub)247 ih_not_missing_callback (inotify_sub *sub)
248 {
249   gint now = g_get_monotonic_time ();
250 
251   g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_CREATED,
252                                       sub->filename, NULL, NULL, now);
253   g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
254                                       sub->filename, NULL, NULL, now);
255 }
256 
257 /* Transforms a inotify event to a GVFS event. */
258 static GFileMonitorEvent
ih_mask_to_EventFlags(guint32 mask)259 ih_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