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
22 #include "watch_interface.h"
24 #include <sys/inotify.h>
37 typedef struct _InoSource InoSource;
38 typedef struct _InoData InoData;
44 GHashTable *targets_by_key;
45 GHashTable *files_by_key;
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);
57 static GSourceFuncs source_funcs = {
64 GSource* watch_sys_create_source(void)
68 InoSource *ino_source;
73 gchar *s = strerror(errno);
74 g_warning("Failed to initialize inotify: %s", s);
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);
90 static gboolean source_prepare(GSource *source, gint *timeout)
96 static gboolean source_check(GSource *source)
98 InoSource *ino_source = (InoSource*)source;
100 return ino_source->pfd.revents;
103 static gboolean source_read(GSource *source, GSourceFunc cb, gpointer data)
105 const gint BUF_LEN = sizeof(struct inotify_event) + 1024;
106 InoSource *ino_source = (InoSource*)source;
107 gchar buffer[BUF_LEN];
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 */
118 struct inotify_event event;
121 state = READING_DONE;
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;
129 while (state != READING_DONE) {
130 if (pos == len || !len) {
131 /* refill the buffer */
134 len = read(ino_source->pfd.fd, &buffer[pos], BUF_LEN-pos);
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 */
143 g_warning("Interrupted reading from inotify");
148 /*g_debug("Done reading from inotify");*/
152 /*g_debug("read %d bytes", len);*/
155 if (state == READING_EVENT) {
156 const guint remain = len - pos;
158 if (remain < sizeof(struct inotify_event)) {
159 /* there is more of the event struct to read */
161 for (i = 0; i < remain; ++i)
162 buffer[i] = buffer[pos+i];
163 /*g_debug("leftover %d bytes of event struct", remain);*/
166 event = *(struct inotify_event*)&buffer[pos];
167 pos += sizeof(struct inotify_event);
169 /*g_debug("read event: wd %d mask %x len %d",
170 event.wd, event.mask, event.len);*/
172 if (remain >= event.len) {
173 /*g_debug("name fits in buffer");*/
174 state = READING_NAME_BUFFER;
176 name_len = event.len;
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);
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;
197 if ((state == READING_NAME_BUFFER || state == READING_NAME_HEAP) &&
198 name_len == event.len)
200 /* done reading the file name ! */
204 g_debug("read filename %s mask %x", name, event.mask);
206 g_debug("read no filename mask %x", event.mask);
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 */
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);
223 name_split = g_strsplit(name, G_DIR_SEPARATOR_S, 0);
225 name_split = g_strsplit("", G_DIR_SEPARATOR_S, 0);
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);
234 watch_main_notify_add(t, parent, *c);
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)
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);
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);
255 g_assert_not_reached();
257 /* call the GSource callback if there is one */
261 g_strfreev(name_split);
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()
269 if (state == READING_NAME_HEAP)
271 /* is there another event in the buffer after this one */
273 state = READING_DONE;
275 state = READING_EVENT;
281 static void source_finalize(GSource *source)
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);
291 gpointer watch_sys_add_file(GSource *source,
292 ObtWatchTarget *target,
296 InoSource *ino_source;
302 ino_source = (InoSource*)source;
305 mask = IN_MODIFY | IN_CREATE | IN_DELETE | IN_MOVE;
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;
312 path = watch_main_target_file_full_path(target, file);
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);*/
319 gchar *s = strerror(errno);
320 g_warning("Unable to watch path %s: %s", path, s);
323 ino_data = g_slice_new(InoData);
325 g_hash_table_insert(ino_source->targets_by_key,
328 g_hash_table_insert(ino_source->files_by_key,
338 void watch_sys_remove_file(GSource *source,
339 ObtWatchTarget *target,
343 InoSource *ino_source;
347 ino_source = (InoSource*)source;
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);
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);