1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 obt/watch.c for the Openbox window manager
4 Copyright (c) 2010 Dana Jansens
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.
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.
16 See the COPYING file for a copy of the GNU General Public License.
19 #ifdef HAVE_SYS_INOTIFY_H
23 #include <sys/inotify.h>
36 typedef struct _InoSource InoSource;
37 typedef struct _InoTarget InoTarget;
39 /*! Callback function in the watch general system.
40 Matches definition in watch.c
42 typedef void (*ObtWatchNotifyFunc)(const gchar *path, gpointer target,
43 ObtWatchNotifyType type);
49 ObtWatchNotifyFunc notify;
50 GHashTable *targets_by_key;
51 GHashTable *targets_by_path;
57 gpointer watch_target;
59 gboolean watch_hidden;
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,
69 static void remove_target(GSource *source, InoTarget *target);
70 static void target_free(InoTarget *target);
72 static GSourceFuncs source_funcs = {
79 gint watch_sys_add_target(GSource *source, const char *path,
80 gboolean watch_hidden, gpointer target)
82 return add_target(source, NULL, path, watch_hidden, target);
85 void watch_sys_remove_target(GSource *source, gint key)
87 InoSource *ino_source = (InoSource*)source;
90 t = g_hash_table_lookup(ino_source->targets_by_key, &key);
91 remove_target(source, t);
94 GSource* watch_sys_create_source(ObtWatchNotifyFunc notify)
98 InoSource *ino_source;
100 g_return_val_if_fail(notify != NULL, NULL);
105 gchar *s = strerror(errno);
106 g_warning("Failed to initialize inotify: %s", s);
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);
124 static gboolean source_prepare(GSource *source, gint *timeout)
130 static gboolean source_check(GSource *source)
135 static gboolean source_read(GSource *source, GSourceFunc cb, gpointer data)
137 const gint BUF_LEN = sizeof(struct inotify_event) + 1024;
138 InoSource *ino_source = (InoSource*)source;
139 gchar buffer[BUF_LEN];
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 */
150 struct inotify_event event;
153 state = READING_EVENT;
154 len = event_len = name_len = 0;
157 if (pos == len || !len || event_len) {
158 /* refill the buffer */
165 len = read(ino_source->pfd.fd, &buffer[pos], BUF_LEN-pos);
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 */
173 g_debug("Done reading from inotify");
177 g_debug("read %d bytes", len);
180 if (state == READING_EVENT) {
181 const guint remain = len - pos;
183 if (remain < sizeof(struct inotify_event)) {
184 /* there is more of the event struct to read */
186 for (i = 0; i < remain; ++i)
187 buffer[i] = buffer[pos+i];
188 g_debug("leftover %d bytes of event struct", remain);
191 event = *(struct inotify_event*)&buffer[pos];
192 pos += sizeof(struct inotify_event);
194 g_debug("read event: wd %d mask %x len %d",
195 event.wd, event.mask, event.len);
197 if (remain >= event.len) {
198 g_debug("name fits in buffer");
199 state = READING_NAME_BUFFER;
201 name_len = event.len;
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);
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;
222 if ((state == READING_NAME_BUFFER || state == READING_NAME_HEAP) &&
223 name_len == event.len)
225 /* done reading the file name ! */
228 ObtWatchNotifyType type;
231 g_debug("read filename %s mask %x", name, event.mask);
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 */
238 t = g_hash_table_lookup(ino_source->targets_by_key, &event.wd);
241 full_path = g_build_filename(t->path, name, NULL);
242 g_debug("full path to change: %s", full_path);
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;
250 else if (event.mask & IN_ISDIR) {
251 if (event.mask & IN_MOVED_TO ||
252 event.mask & IN_CREATE)
254 add_target(source, t, full_path, t->watch_hidden,
256 g_debug("added %s", full_path);
258 else if (event.mask & IN_MOVED_FROM ||
259 event.mask & IN_DELETE)
263 subt = g_hash_table_lookup(ino_source->targets_by_path,
266 remove_target(source, subt);
267 g_debug("removed %s", full_path);
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;
280 g_assert_not_reached();
284 /* call the GSource callback if there is one */
287 /* call the WatchNotify callback */
288 ino_source->notify(full_path, t->watch_target, type);
294 if (state == READING_NAME_HEAP)
296 state = READING_EVENT;
301 static void source_finalize(GSource *source)
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);
309 static gint add_target(GSource *source, InoTarget *parent,
310 const gchar *path, gboolean watch_hidden,
313 InoSource *ino_source;
314 InoTarget *ino_target;
319 ino_source = (InoSource*)source;
321 is_dir = g_file_test(path, G_FILE_TEST_IS_DIR);
323 mask = IN_MODIFY | IN_CREATE | IN_DELETE | IN_MOVE;
326 /* only watch IN_MOVE_SELF on the top-most target of the watch */
328 mask |= IN_MOVE_SELF;
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);
335 gchar *s = strerror(errno);
336 g_warning("Unable to watch path %s: %s", path, s);
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,
347 g_hash_table_insert(ino_source->targets_by_path, ino_target->path,
351 if (key >= 0 && is_dir) {
355 dir = g_dir_open(path, 0, NULL);
359 while ((name = g_dir_read_name(dir))) {
360 if (name[0] != '.' || watch_hidden) {
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,
368 /* notify for each file in the directory on startup */
369 ino_source->notify(subpath, ino_target->watch_target,
381 static void remove_target(GSource *source, InoTarget *target)
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);
390 static void target_free(InoTarget *target)
392 g_free(target->path);
393 g_slice_free(InoTarget, target);