]> icculus.org git repositories - dana/openbox.git/blob - obt/watch_inotify.c
Make warnings about parse problems in .desktop files "debug" messages. Most people...
[dana/openbox.git] / obt / watch_inotify.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    obt/watch.c for the Openbox window manager
4    Copyright (c) 2010        Dana Jansens
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    See the COPYING file for a copy of the GNU General Public License.
17 */
18
19 #ifdef HAVE_SYS_INOTIFY_H
20
21 #include "watch.h"
22 #include "watch_interface.h"
23
24 #include <sys/inotify.h>
25 #ifdef HAVE_UNISTD_H
26 #  include <unistd.h>
27 #endif
28 #ifdef HAVE_STRING_H
29 #  include <string.h>
30 #endif
31 #ifdef HAVE_ERRNO_H
32 #  include <errno.h>
33 #endif
34
35 #include <glib.h>
36
37 typedef struct _InoSource InoSource;
38 typedef struct _InoData InoData;
39
40 struct _InoSource {
41     GSource source;
42
43     GPollFD pfd;
44     GHashTable *targets_by_key;
45     GHashTable *files_by_key;
46 };
47
48 struct _InoData {
49     gint key;
50 };
51
52 static gboolean source_check(GSource *source);
53 static gboolean source_prepare(GSource *source, gint *timeout);
54 static gboolean source_read(GSource *source, GSourceFunc cb, gpointer data);
55 static void source_finalize(GSource *source);
56
57 static GSourceFuncs source_funcs = {
58     source_prepare,
59     source_check,
60     source_read,
61     source_finalize
62 };
63
64 GSource* watch_sys_create_source(void)
65 {
66     gint fd;
67     GSource *source;
68     InoSource *ino_source;
69
70     source = NULL;
71     fd = inotify_init();
72     if (fd < 0) {
73         gchar *s = strerror(errno);
74         g_warning("Failed to initialize inotify: %s", s);
75     }
76     else {
77         /*g_debug("initialized inotify on fd %u", fd);*/
78         source = g_source_new(&source_funcs, sizeof(InoSource));
79         ino_source = (InoSource*)source;
80         ino_source->targets_by_key = g_hash_table_new_full(
81             g_int_hash, g_int_equal, NULL, NULL);
82         ino_source->files_by_key = g_hash_table_new_full(
83             g_int_hash, g_int_equal, NULL, NULL);
84         ino_source->pfd = (GPollFD){ fd, G_IO_IN, 0 };
85         g_source_add_poll(source, &ino_source->pfd);
86     }
87     return source;
88 }
89
90 static gboolean source_prepare(GSource *source, gint *timeout)
91 {
92     *timeout = -1;
93     return FALSE;
94 }
95
96 static gboolean source_check(GSource *source)
97 {
98     InoSource *ino_source = (InoSource*)source;
99
100     return ino_source->pfd.revents;
101 }
102
103 static gboolean source_read(GSource *source, GSourceFunc cb, gpointer data)
104 {
105     const gint BUF_LEN = sizeof(struct inotify_event) + 1024;
106     InoSource *ino_source = (InoSource*)source;
107     gchar buffer[BUF_LEN];
108     gchar *name;
109     guint name_len; /* number of bytes read for the name */
110     gint len; /* number of bytes in the buffer */
111     gint pos; /* position in the buffer */
112     enum {
113         READING_DONE,
114         READING_EVENT,
115         READING_NAME_BUFFER,
116         READING_NAME_HEAP
117     } state;
118     struct inotify_event event;
119
120     pos = BUF_LEN;
121     state = READING_DONE;
122     len = name_len = 0;
123
124     /* sometimes we can end up here even tho no events have been reported by
125        the kernel.  blame glib?  but we'll block if we read in that case. */
126     if (ino_source->pfd.revents)
127         state = READING_EVENT;
128
129     while (state != READING_DONE) {
130         if (pos == len || !len) {
131             /* refill the buffer */
132
133             pos = 0;
134             len = read(ino_source->pfd.fd, &buffer[pos], BUF_LEN-pos);
135
136             if (len < 0) {
137                 if (errno != EINTR) {
138                     gchar *s = strerror(errno);
139                     g_warning("Failed to read from inotify: %s", s);
140                     return FALSE; /* won't read any more */
141                 }
142                 else {
143                     g_warning("Interrupted reading from inotify");
144                     return TRUE;
145                 }
146             }
147             if (len == 0) {
148                 /*g_debug("Done reading from inotify");*/
149                 return TRUE;
150             }
151
152             /*g_debug("read %d bytes", len);*/
153         }
154
155         if (state == READING_EVENT) {
156             const guint remain = len - pos;
157
158             if (remain < sizeof(struct inotify_event)) {
159                 /* there is more of the event struct to read */
160                 guint i;
161                 for (i = 0; i < remain; ++i)
162                     buffer[i] = buffer[pos+i];
163                 /*g_debug("leftover %d bytes of event struct", remain);*/
164             }
165             else {
166                 event = *(struct inotify_event*)&buffer[pos];
167                 pos += sizeof(struct inotify_event);
168
169                 /*g_debug("read event: wd %d mask %x len %d",
170                           event.wd, event.mask, event.len);*/
171
172                 if (remain >= event.len) {
173                     /*g_debug("name fits in buffer");*/
174                     state = READING_NAME_BUFFER;
175                     name = &buffer[pos];
176                     name_len = event.len;
177                     pos += event.len;
178                 }
179                 else { /* remain < event.len */
180                     /*g_debug("name doesn't fit in buffer");*/
181                     state = READING_NAME_HEAP;
182                     name = g_new(gchar, event.len);
183                     memcpy(name, &buffer[pos], remain);
184                     name_len = remain;
185                     pos += remain;
186                 }
187             }
188         }
189         if (state == READING_NAME_HEAP && pos < len) {
190             const guint buf_remain = len - pos;
191             const guint name_remain = event.len - name_len;
192             const guint copy_len = MIN(buf_remain, name_remain);
193             memcpy(name+name_len, &buffer[pos], copy_len);
194             name_len += copy_len;
195             pos += copy_len;
196         }
197         if ((state == READING_NAME_BUFFER || state == READING_NAME_HEAP) &&
198             name_len == event.len)
199         {
200             /* done reading the file name ! */
201
202             /*
203             if (event.len)
204                 g_debug("read filename %s mask %x", name, event.mask);
205             else
206                 g_debug("read no filename mask %x", event.mask);
207             */
208
209             event.mask &= ~IN_IGNORED;  /* skip this one, we watch for things
210                                            to get removed explicitly so this
211                                            will just be double-reporting */
212             if (event.mask) {
213                 ObtWatchTarget *t;
214                 ObtWatchFile *f;
215                 gchar **name_split;
216                 gchar **c;
217
218                 f = g_hash_table_lookup(ino_source->files_by_key, &event.wd);
219                 t = g_hash_table_lookup(ino_source->targets_by_key, &event.wd);
220                 g_assert(f != NULL);
221
222                 if (event.len)
223                     name_split = g_strsplit(name, G_DIR_SEPARATOR_S, 0);
224                 else
225                     name_split = g_strsplit("", G_DIR_SEPARATOR_S, 0);
226
227                 if (f) {
228                     if (event.mask & IN_MOVED_TO || event.mask & IN_CREATE) {
229                         ObtWatchFile *parent = f;
230                         for (c = name_split; *(c+1); ++c) {
231                             parent = watch_main_file_child(parent, *c);
232                             g_assert(parent);
233                         }
234                         watch_main_notify_add(t, parent, *c);
235                     }
236                     else if (event.mask & IN_MOVED_FROM ||
237                              event.mask & IN_MOVE_SELF ||
238                              event.mask & IN_DELETE ||
239                              event.mask & IN_DELETE_SELF)
240                     {
241                         ObtWatchFile *file = f;
242                         for (c = name_split; *c; ++c)
243                             file = watch_main_file_child(file, *c);
244                         if (file) /* may not have been tracked */
245                             watch_main_notify_remove(t, file);
246                     }
247                     else if (event.mask & IN_MODIFY) {
248                         ObtWatchFile *file = f;
249                         for (c = name_split; *c; ++c)
250                             file = watch_main_file_child(file, *c);
251                         if (file) /* may not have been tracked */
252                             watch_main_notify_modify(t, file);
253                     }
254                     else
255                         g_assert_not_reached();
256
257                     /* call the GSource callback if there is one */
258                     if (cb) cb(data);
259                 }
260
261                 g_strfreev(name_split);
262
263                 /* only read one event at a time, so poll can tell us if there
264                    is another one ready, and we don't block on the read()
265                    needlessly. */
266                 break;
267             }
268
269             if (state == READING_NAME_HEAP)
270                 g_free(name);
271             /* is there another event in the buffer after this one */
272             if (pos == len)
273                 state = READING_DONE;
274             else
275                 state = READING_EVENT;
276         }
277     }
278     return TRUE;
279 }
280
281 static void source_finalize(GSource *source)
282 {
283     InoSource *ino_source = (InoSource*)source;
284     /*g_debug("source_finalize");*/
285     close(ino_source->pfd.fd);
286     g_hash_table_destroy(ino_source->targets_by_key);
287     g_hash_table_destroy(ino_source->files_by_key);
288 }
289
290
291 gpointer watch_sys_add_file(GSource *source,
292                             ObtWatchTarget *target,
293                             ObtWatchFile *file,
294                             gboolean is_dir)
295 {
296     InoSource *ino_source;
297     InoData *ino_data;
298     guint32 mask;
299     gint key;
300     gchar *path;
301
302     ino_source = (InoSource*)source;
303
304     if (is_dir)
305         mask = IN_MODIFY | IN_CREATE | IN_DELETE | IN_MOVE;
306     else
307         mask = IN_MODIFY;
308     /* only watch IN_MOVE_SELF on the top-most target of the watch */
309     if (watch_main_target_root(target) == file)
310         mask |= IN_MOVE_SELF | IN_DELETE_SELF;
311
312     path = watch_main_target_file_full_path(target, file);
313
314     ino_data = NULL;
315     key = inotify_add_watch(ino_source->pfd.fd, path, mask);
316     /*g_debug("added watch descriptor %d for fd %d on path %s",
317               key, ino_source->pfd.fd, path);*/
318     if (key < 0) {
319         gchar *s = strerror(errno);
320         g_warning("Unable to watch path %s: %s", path, s);
321     }
322     else {
323         ino_data = g_slice_new(InoData);
324         ino_data->key = key;
325         g_hash_table_insert(ino_source->targets_by_key,
326                             &ino_data->key,
327                             target);
328         g_hash_table_insert(ino_source->files_by_key,
329                             &ino_data->key,
330                             file);
331     }
332
333     g_free(path);
334
335     return ino_data;
336 }
337
338 void watch_sys_remove_file(GSource *source,
339                            ObtWatchTarget *target,
340                            ObtWatchFile *file,
341                            gpointer data)
342 {
343     InoSource *ino_source;
344     InoData *ino_data;
345
346     if (data) {
347         ino_source = (InoSource*)source;
348         ino_data = data;
349
350         /*g_debug("removing wd %d for fd %d", ino_data->key,
351                   ino_source->pfd.fd); */
352         inotify_rm_watch(ino_source->pfd.fd, (guint32)ino_data->key);
353
354         g_hash_table_remove(ino_source->targets_by_key, &ino_data->key);
355         g_hash_table_remove(ino_source->files_by_key, &ino_data->key);
356         g_slice_free(InoData, ino_data);
357     }
358 }
359
360
361 #endif