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 *sub_path,
43 const gchar *full_path, gpointer target,
44 ObtWatchNotifyType type);
50 ObtWatchNotifyFunc notify;
51 GHashTable *targets_by_key;
52 GHashTable *targets_by_path;
58 guint base_len; /* the length of the prefix of path which is the
60 gpointer watch_target;
62 gboolean watch_hidden;
65 static gboolean source_check(GSource *source);
66 static gboolean source_prepare(GSource *source, gint *timeout);
67 static gboolean source_read(GSource *source, GSourceFunc cb, gpointer data);
68 static void source_finalize(GSource *source);
69 static gint add_target(GSource *source, InoTarget *parent,
70 const gchar *path, gboolean watch_hidden,
72 static void remove_target(GSource *source, InoTarget *target);
73 static void target_free(InoTarget *target);
74 static void notify_target(GSource *source, InoTarget *ino_target,
75 const gchar *path, ObtWatchNotifyType type);
77 static GSourceFuncs source_funcs = {
84 gint watch_sys_add_target(GSource *source, const char *path,
85 gboolean watch_hidden, gpointer target)
87 return add_target(source, NULL, path, watch_hidden, target);
90 void watch_sys_remove_target(GSource *source, gint key)
92 InoSource *ino_source = (InoSource*)source;
95 t = g_hash_table_lookup(ino_source->targets_by_key, &key);
96 remove_target(source, t);
99 GSource* watch_sys_create_source(ObtWatchNotifyFunc notify)
103 InoSource *ino_source;
105 g_return_val_if_fail(notify != NULL, NULL);
110 gchar *s = strerror(errno);
111 g_warning("Failed to initialize inotify: %s", s);
114 g_debug("initialized inotify on fd %d", fd);
115 source = g_source_new(&source_funcs, sizeof(InoSource));
116 ino_source = (InoSource*)source;
117 ino_source->notify = notify;
118 ino_source->targets_by_key = g_hash_table_new_full(
119 g_int_hash, g_int_equal, NULL, NULL);
120 ino_source->targets_by_path = g_hash_table_new_full(
121 g_str_hash, g_str_equal, NULL, (GDestroyNotify)target_free);
122 ino_source->pfd = (GPollFD){ fd, G_IO_IN, G_IO_IN };
123 g_source_add_poll(source, &ino_source->pfd);
128 static gboolean source_prepare(GSource *source, gint *timeout)
134 static gboolean source_check(GSource *source)
139 static gboolean source_read(GSource *source, GSourceFunc cb, gpointer data)
141 const gint BUF_LEN = sizeof(struct inotify_event) + 1024;
142 InoSource *ino_source = (InoSource*)source;
143 gchar buffer[BUF_LEN];
145 guint name_len; /* number of bytes read for the name */
146 guint event_len; /* number of bytes read for the event */
147 gint len; /* number of bytes in the buffer */
148 gint pos; /* position in the buffer */
154 struct inotify_event event;
157 state = READING_EVENT;
158 len = event_len = name_len = 0;
160 /* sometimes we can end up here even tho no events have been reported by
161 the kernel. blame glib? but we'll block if we read in that case. */
162 while (ino_source->pfd.revents) {
163 if (pos == len || !len || event_len) {
164 /* refill the buffer */
171 len = read(ino_source->pfd.fd, &buffer[pos], BUF_LEN-pos);
174 if (errno != EINTR) {
175 gchar *s = strerror(errno);
176 g_warning("Failed to read from inotify: %s", s);
177 return FALSE; /* won't read any more */
180 g_warning("Interrupted reading from inotify");
185 g_debug("Done reading from inotify");
189 g_debug("read %d bytes", len);
192 if (state == READING_EVENT) {
193 const guint remain = len - pos;
195 if (remain < sizeof(struct inotify_event)) {
196 /* there is more of the event struct to read */
198 for (i = 0; i < remain; ++i)
199 buffer[i] = buffer[pos+i];
200 g_debug("leftover %d bytes of event struct", remain);
203 event = *(struct inotify_event*)&buffer[pos];
204 pos += sizeof(struct inotify_event);
206 g_debug("read event: wd %d mask %x len %d",
207 event.wd, event.mask, event.len);
209 if (remain >= event.len) {
210 g_debug("name fits in buffer");
211 state = READING_NAME_BUFFER;
213 name_len = event.len;
216 else { /* remain < event.len */
217 g_debug("name doesn't fit in buffer");
218 state = READING_NAME_HEAP;
219 name = g_new(gchar, event.len);
220 memcpy(name, &buffer[pos], remain);
226 if (state == READING_NAME_HEAP && pos < len) {
227 const guint buf_remain = len - pos;
228 const guint name_remain = event.len - name_len;
229 const guint copy_len = MIN(buf_remain, name_remain);
230 memcpy(name+name_len, &buffer[pos], copy_len);
231 name_len += copy_len;
234 if ((state == READING_NAME_BUFFER || state == READING_NAME_HEAP) &&
235 name_len == event.len)
237 /* done reading the file name ! */
240 ObtWatchNotifyType type;
243 g_debug("read filename %s mask %x", name, event.mask);
245 event.mask &= ~IN_IGNORED; /* skip this one, we watch for things
246 to get removed explicitly so this
247 will just be double-reporting */
250 t = g_hash_table_lookup(ino_source->targets_by_key, &event.wd);
253 full_path = g_build_filename(t->path, name, NULL);
254 g_debug("full path to change: %s", full_path);
256 /* don't report hidden stuff inside a directory watch */
257 report = !t->is_dir || name[0] != '.' || t->watch_hidden;
258 if (event.mask & IN_MOVE_SELF) {
259 g_warning("Watched target was moved away: %s", t->path);
260 type = OBT_WATCH_SELF_REMOVED;
262 else if (event.mask & IN_ISDIR) {
263 if (event.mask & IN_MOVED_TO ||
264 event.mask & IN_CREATE)
266 add_target(source, t, full_path, t->watch_hidden,
268 g_debug("added %s", full_path);
270 else if (event.mask & IN_MOVED_FROM ||
271 event.mask & IN_DELETE)
275 subt = g_hash_table_lookup(ino_source->targets_by_path,
278 remove_target(source, subt);
279 g_debug("removed %s", full_path);
284 if (event.mask & IN_MOVED_TO || event.mask & IN_CREATE)
285 type = OBT_WATCH_ADDED;
286 else if (event.mask & IN_MOVED_FROM ||
287 event.mask & IN_DELETE)
288 type = OBT_WATCH_REMOVED;
289 else if (event.mask & IN_MODIFY)
290 type = OBT_WATCH_MODIFIED;
292 g_assert_not_reached();
296 /* call the GSource callback if there is one */
299 /* call the WatchNotify callback */
300 notify_target(source, t, full_path, type);
305 /* only read one event at a time, so poll can tell us if there
306 is another one ready, and we don't block on the read()
311 if (state == READING_NAME_HEAP)
313 state = READING_EVENT;
318 static void source_finalize(GSource *source)
320 InoSource *ino_source = (InoSource*)source;
321 g_debug("source_finalize");
322 close(ino_source->pfd.fd);
323 g_hash_table_destroy(ino_source->targets_by_key);
326 static gint add_target(GSource *source, InoTarget *parent,
328 gboolean watch_hidden, gpointer target)
330 InoSource *ino_source;
331 InoTarget *ino_target;
336 ino_source = (InoSource*)source;
338 is_dir = g_file_test(path, G_FILE_TEST_IS_DIR);
340 mask = IN_MODIFY | IN_CREATE | IN_DELETE | IN_MOVE;
343 /* only watch IN_MOVE_SELF on the top-most target of the watch */
345 mask |= IN_MOVE_SELF;
348 key = inotify_add_watch(ino_source->pfd.fd, path, mask);
349 g_debug("added watch descriptor %d for fd %d on path %s",
350 key, ino_source->pfd.fd, path);
352 gchar *s = strerror(errno);
353 g_warning("Unable to watch path %s: %s", path, s);
356 ino_target = g_slice_new(InoTarget);
357 ino_target->key = key;
358 ino_target->path = g_strdup(path);
359 ino_target->base_len = (parent ? parent->base_len : strlen(path));
360 ino_target->is_dir = is_dir;
361 ino_target->watch_hidden = watch_hidden;
362 ino_target->watch_target = target;
363 g_hash_table_insert(ino_source->targets_by_key, &ino_target->key,
365 g_hash_table_insert(ino_source->targets_by_path, ino_target->path,
369 if (key >= 0 && is_dir) {
373 dir = g_dir_open(path, 0, NULL);
377 while ((name = g_dir_read_name(dir))) {
378 if (name[0] != '.' || watch_hidden) {
381 subpath = g_build_filename(path, name, NULL);
382 if (g_file_test(subpath, G_FILE_TEST_IS_DIR))
383 add_target(source, ino_target, subpath,
384 watch_hidden, target);
386 /* notify for each file in the directory on startup */
387 notify_target(source, ino_target, subpath,
399 static void remove_target(GSource *source, InoTarget *target)
401 InoSource *ino_source = (InoSource*)source;
402 g_debug("removing wd %d for fd %d", target->key, ino_source->pfd.fd);
403 inotify_rm_watch(ino_source->pfd.fd, (guint32)target->key);
404 g_hash_table_remove(ino_source->targets_by_key, &target->key);
405 g_hash_table_remove(ino_source->targets_by_path, target->path);
408 static void target_free(InoTarget *target)
410 g_free(target->path);
411 g_slice_free(InoTarget, target);
414 static void notify_target(GSource *source, InoTarget *ino_target,
415 const gchar *path, ObtWatchNotifyType type)
417 InoSource *ino_source = (InoSource*)source;
418 ino_source->notify(path + ino_target->base_len,
420 ino_target->watch_target,