]> icculus.org git repositories - dana/openbox.git/blob - obt/watch_inotify.c
Add linkbase which will keep track of available .desktop files for application launch...
[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
23 #include <sys/inotify.h>
24 #ifdef HAVE_UNISTD_H
25 #  include <unistd.h>
26 #endif
27 #ifdef HAVE_STRING_H
28 #  include <string.h>
29 #endif
30 #ifdef HAVE_ERRNO_H
31 #  include <errno.h>
32 #endif
33
34 #include <glib.h>
35
36 typedef struct _InoSource InoSource;
37 typedef struct _InoTarget InoTarget;
38
39 /*! Callback function in the watch general system.
40   Matches definition in watch.c
41 */
42 typedef void (*ObtWatchNotifyFunc)(const gchar *path, gpointer target,
43                                    ObtWatchNotifyType type);
44
45 struct _InoSource {
46     GSource source;
47
48     GPollFD pfd;
49     ObtWatchNotifyFunc notify;
50     GHashTable *targets_by_key;
51     GHashTable *targets_by_path;
52 };
53
54 struct _InoTarget {
55     gint key;
56     gchar *path;
57     gpointer watch_target;
58     gboolean is_dir;
59     gboolean watch_hidden;
60 };
61
62 static gboolean source_check(GSource *source);
63 static gboolean source_prepare(GSource *source, gint *timeout);
64 static gboolean source_read(GSource *source, GSourceFunc cb, gpointer data);
65 static void source_finalize(GSource *source);
66 static gint add_target(GSource *source, InoTarget *parent,
67                        const gchar *path, gboolean watch_hidden,
68                        gpointer target);
69 static void remove_target(GSource *source, InoTarget *target);
70 static void target_free(InoTarget *target);
71
72 static GSourceFuncs source_funcs = {
73     source_prepare,
74     source_check,
75     source_read,
76     source_finalize
77 };
78
79 gint watch_sys_add_target(GSource *source, const char *path,
80                           gboolean watch_hidden, gpointer target)
81 {
82     return add_target(source, NULL, path, watch_hidden, target);
83 }
84
85 void watch_sys_remove_target(GSource *source, gint key)
86 {
87     InoSource *ino_source = (InoSource*)source;
88     InoTarget *t;
89
90     t = g_hash_table_lookup(ino_source->targets_by_key, &key);
91     remove_target(source, t);
92 }
93
94 GSource* watch_sys_create_source(ObtWatchNotifyFunc notify)
95 {
96     gint fd;
97     GSource *source;
98     InoSource *ino_source;
99
100     g_return_val_if_fail(notify != NULL, NULL);
101
102     source = NULL;
103     fd = inotify_init();
104     if (fd < 0) {
105         gchar *s = strerror(errno);
106         g_warning("Failed to initialize inotify: %s", s);
107     }
108     else {
109         g_debug("initialized inotify on fd %d", fd);
110         source = g_source_new(&source_funcs, sizeof(InoSource));
111         ino_source = (InoSource*)source;
112         ino_source->notify = notify;
113         ino_source->targets_by_key = g_hash_table_new_full(
114             g_int_hash, g_int_equal, NULL, NULL);
115         ino_source->targets_by_path = g_hash_table_new_full(
116             g_str_hash, g_str_equal, NULL, (GDestroyNotify)target_free);
117         ino_source->pfd = (GPollFD){ fd, G_IO_IN, G_IO_IN };
118         g_source_add_poll(source, &ino_source->pfd);
119         g_source_attach(source, NULL);
120     }
121     return source;
122 }
123
124 static gboolean source_prepare(GSource *source, gint *timeout)
125 {
126     *timeout = -1;
127     return FALSE;
128 }
129
130 static gboolean source_check(GSource *source)
131 {
132     return TRUE;
133 }
134
135 static gboolean source_read(GSource *source, GSourceFunc cb, gpointer data)
136 {
137     const gint BUF_LEN = sizeof(struct inotify_event) + 1024;
138     InoSource *ino_source = (InoSource*)source;
139     gchar buffer[BUF_LEN];
140     gchar *name;
141     guint name_len; /* number of bytes read for the name */
142     guint event_len; /* number of bytes read for the event */
143     gint len; /* number of bytes in the buffer */
144     gint pos; /* position in the buffer */
145     enum {
146         READING_EVENT,
147         READING_NAME_BUFFER,
148         READING_NAME_HEAP
149     } state;
150     struct inotify_event event;
151
152     pos = BUF_LEN;
153     state = READING_EVENT;
154     len = event_len = name_len = 0;
155
156     while (TRUE) {
157         if (pos == len || !len || event_len) {
158             /* refill the buffer */
159
160             if (event_len)
161                 pos = event_len;
162             else
163                 pos = 0;
164
165             len = read(ino_source->pfd.fd, &buffer[pos], BUF_LEN-pos);
166
167             if (len < 0 && errno != EINTR) {
168                 gchar *s = strerror(errno);
169                 g_warning("Failed to read from inotify: %s", s);
170                 return FALSE; /* won't read any more */
171             }
172             if (len == 0) {
173                 g_debug("Done reading from inotify");
174                 return TRUE;
175             }
176
177             g_debug("read %d bytes", len);
178         }
179
180         if (state == READING_EVENT) {
181             const guint remain = len - pos;
182
183             if (remain < sizeof(struct inotify_event)) {
184                 /* there is more of the event struct to read */
185                 guint i;
186                 for (i = 0; i < remain; ++i)
187                     buffer[i] = buffer[pos+i];
188                 g_debug("leftover %d bytes of event struct", remain);
189             }
190             else {
191                 event = *(struct inotify_event*)&buffer[pos];
192                 pos += sizeof(struct inotify_event);
193
194                 g_debug("read event: wd %d mask %x len %d",
195                         event.wd, event.mask, event.len);
196
197                 if (remain >= event.len) {
198                     g_debug("name fits in buffer");
199                     state = READING_NAME_BUFFER;
200                     name = &buffer[pos];
201                     name_len = event.len;
202                     pos += event.len;
203                 }
204                 else { /* remain < event.len */
205                     g_debug("name doesn't fit in buffer");
206                     state = READING_NAME_HEAP;
207                     name = g_new(gchar, event.len);
208                     memcpy(name, &buffer[pos], remain);
209                     name_len = remain;
210                     pos += remain;
211                 }
212             }
213         }
214         if (state == READING_NAME_HEAP && pos < len) {
215             const guint buf_remain = len - pos;
216             const guint name_remain = event.len - name_len;
217             const guint copy_len = MIN(buf_remain, name_remain);
218             memcpy(name+name_len, &buffer[pos], copy_len);
219             name_len += copy_len;
220             pos += copy_len;
221         }
222         if ((state == READING_NAME_BUFFER || state == READING_NAME_HEAP) &&
223             name_len == event.len)
224         {
225             /* done reading the file name ! */
226             InoTarget *t;
227             gboolean report;
228             ObtWatchNotifyType type;
229             gchar *full_path;
230
231             g_debug("read filename %s mask %x", name, event.mask);
232
233             event.mask &= ~IN_IGNORED;  /* skip this one, we watch for things
234                                            to get removed explicitly so this
235                                            will just be double-reporting */
236             if (event.mask) {
237
238                 t = g_hash_table_lookup(ino_source->targets_by_key, &event.wd);
239                 g_assert(t != NULL);
240
241                 full_path = g_build_filename(t->path, name, NULL);
242                 g_debug("full path to change: %s", full_path);
243
244                 /* don't report hidden stuff inside a directory watch */
245                 report = !t->is_dir || name[0] != '.' || t->watch_hidden;
246                 if (event.mask & IN_MOVE_SELF) {
247                     g_warning("Watched target was moved away: %s", t->path);
248                     type = OBT_WATCH_SELF_REMOVED;
249                 }
250                 else if (event.mask & IN_ISDIR) {
251                     if (event.mask & IN_MOVED_TO ||
252                         event.mask & IN_CREATE)
253                     {
254                         add_target(source, t, full_path, t->watch_hidden,
255                                    t->watch_target);
256                         g_debug("added %s", full_path);
257                     }
258                     else if (event.mask & IN_MOVED_FROM ||
259                              event.mask & IN_DELETE)
260                     {
261                         InoTarget *subt;
262
263                         subt = g_hash_table_lookup(ino_source->targets_by_path,
264                                                    full_path);
265                         g_assert(subt);
266                         remove_target(source, subt);
267                         g_debug("removed %s", full_path);
268                     }
269                     report = FALSE;
270                 }
271                 else {
272                     if (event.mask & IN_MOVED_TO || event.mask & IN_CREATE)
273                         type = OBT_WATCH_ADDED;
274                     else if (event.mask & IN_MOVED_FROM ||
275                              event.mask & IN_DELETE)
276                         type = OBT_WATCH_REMOVED;
277                     else if (event.mask & IN_MODIFY)
278                         type = OBT_WATCH_MODIFIED;
279                     else
280                         g_assert_not_reached();
281                 }
282
283                 if (report) {
284                     /* call the GSource callback if there is one */
285                     if (cb) cb(data);
286
287                     /* call the WatchNotify callback */
288                     ino_source->notify(full_path, t->watch_target, type);
289                 }
290
291                 g_free(full_path);
292             }
293
294             if (state == READING_NAME_HEAP)
295                 g_free(name);
296             state = READING_EVENT;
297         }
298     }
299 }
300
301 static void source_finalize(GSource *source)
302 {
303     InoSource *ino_source = (InoSource*)source;
304     g_debug("source_finalize");
305     close(ino_source->pfd.fd);
306     g_hash_table_destroy(ino_source->targets_by_key);
307 }
308
309 static gint add_target(GSource *source, InoTarget *parent,
310                        const gchar *path, gboolean watch_hidden,
311                        gpointer target)
312 {
313     InoSource *ino_source;
314     InoTarget *ino_target;
315     guint32 mask;
316     gint key;
317     gboolean is_dir;
318
319     ino_source = (InoSource*)source;
320
321     is_dir = g_file_test(path, G_FILE_TEST_IS_DIR);
322     if (is_dir)
323         mask = IN_MODIFY | IN_CREATE | IN_DELETE | IN_MOVE;
324     else
325         mask = IN_MODIFY;
326     /* only watch IN_MOVE_SELF on the top-most target of the watch */
327     if (!parent)
328         mask |= IN_MOVE_SELF;
329
330     ino_target = NULL;
331     key = inotify_add_watch(ino_source->pfd.fd, path, mask);
332     g_debug("added watch descriptor %d for fd %d on path %s",
333             key, ino_source->pfd.fd, path);
334     if (key < 0) {
335         gchar *s = strerror(errno);
336         g_warning("Unable to watch path %s: %s", path, s);
337     }
338     else {
339         ino_target = g_slice_new(InoTarget);
340         ino_target->key = key;
341         ino_target->path = g_strdup(path);
342         ino_target->is_dir = is_dir;
343         ino_target->watch_hidden = watch_hidden;
344         ino_target->watch_target = target;
345         g_hash_table_insert(ino_source->targets_by_key, &ino_target->key,
346                             ino_target);
347         g_hash_table_insert(ino_source->targets_by_path, ino_target->path,
348                             ino_target);
349     }
350
351     if (key >= 0 && is_dir) {
352         /* recurse */
353         GDir *dir;
354
355         dir = g_dir_open(path, 0, NULL);
356         if (dir) {
357             const gchar *name;
358
359             while ((name = g_dir_read_name(dir))) {
360                 if (name[0] != '.' || watch_hidden) {
361                     gchar *subpath;
362
363                     subpath = g_build_filename(path, name, NULL);
364                     if (g_file_test(subpath, G_FILE_TEST_IS_DIR))
365                         add_target(source, ino_target, subpath, watch_hidden,
366                                    target);
367                     else
368                         /* notify for each file in the directory on startup */
369                         ino_source->notify(subpath, ino_target->watch_target,
370                                            OBT_WATCH_ADDED);
371                     g_free(subpath);
372                 }
373             }
374         }
375         g_dir_close(dir);
376     }
377
378     return key;
379 }
380
381 static void remove_target(GSource *source, InoTarget *target)
382 {
383     InoSource *ino_source = (InoSource*)source;
384     g_debug("removing wd %d for fd %d", target->key, ino_source->pfd.fd);
385     inotify_rm_watch(ino_source->pfd.fd, (guint32)target->key);
386     g_hash_table_remove(ino_source->targets_by_key, &target->key);
387     g_hash_table_remove(ino_source->targets_by_path, target->path);
388 }
389
390 static void target_free(InoTarget *target)
391 {
392     g_free(target->path);
393     g_slice_free(InoTarget, target);
394 }
395
396 #endif