2 * QEMU file monitor Linux inotify impl
4 * Copyright (c) 2018 Red Hat, Inc.
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library 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 GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
21 #include "qemu/osdep.h"
22 #include "qemu/filemonitor.h"
23 #include "qemu/main-loop.h"
24 #include "qemu/error-report.h"
25 #include "qapi/error.h"
28 #include <sys/inotify.h>
32 QemuMutex lock; /* protects dirs & idmap */
33 GHashTable *dirs; /* dirname => QFileMonitorDir */
34 GHashTable *idmap; /* inotify ID => dirname */
39 int64_t id; /* watch ID */
40 char *filename; /* optional filter */
41 QFileMonitorHandler cb;
48 int inotify_id; /* inotify ID */
49 int next_file_id; /* file ID counter */
50 GArray *watches; /* QFileMonitorWatch elements */
54 static void qemu_file_monitor_watch(void *arg)
56 QFileMonitor *mon = arg;
58 __attribute__ ((aligned(__alignof__(struct inotify_event))));
62 qemu_mutex_lock(&mon->lock);
65 qemu_mutex_unlock(&mon->lock);
69 len = read(mon->fd, buf, sizeof(buf));
72 if (errno != EAGAIN) {
73 error_report("Failure monitoring inotify FD '%s',"
74 "disabling events", strerror(errno));
78 /* no more events right now */
82 /* Loop over all events in the buffer */
84 struct inotify_event *ev =
85 (struct inotify_event *)(buf + used);
86 const char *name = ev->len ? ev->name : "";
87 QFileMonitorDir *dir = g_hash_table_lookup(mon->idmap,
88 GINT_TO_POINTER(ev->wd));
89 uint32_t iev = ev->mask &
90 (IN_CREATE | IN_MODIFY | IN_DELETE | IN_IGNORED |
91 IN_MOVED_TO | IN_MOVED_FROM | IN_ATTRIB);
95 used += sizeof(struct inotify_event) + ev->len;
102 * During a rename operation, the old name gets
103 * IN_MOVED_FROM and the new name gets IN_MOVED_TO.
104 * To simplify life for callers, we turn these into
105 * DELETED and CREATED events
110 qev = QFILE_MONITOR_EVENT_CREATED;
113 qev = QFILE_MONITOR_EVENT_MODIFIED;
117 qev = QFILE_MONITOR_EVENT_DELETED;
120 qev = QFILE_MONITOR_EVENT_ATTRIBUTES;
123 qev = QFILE_MONITOR_EVENT_IGNORED;
126 g_assert_not_reached();
129 trace_qemu_file_monitor_event(mon, dir->path, name, ev->mask,
131 for (i = 0; i < dir->watches->len; i++) {
132 QFileMonitorWatch *watch = &g_array_index(dir->watches,
136 if (watch->filename == NULL ||
137 (name && g_str_equal(watch->filename, name))) {
138 trace_qemu_file_monitor_dispatch(mon, dir->path, name,
140 watch->opaque, watch->id);
141 watch->cb(watch->id, qev, name, watch->opaque);
147 qemu_mutex_unlock(&mon->lock);
152 qemu_file_monitor_dir_free(void *data)
154 QFileMonitorDir *dir = data;
157 for (i = 0; i < dir->watches->len; i++) {
158 QFileMonitorWatch *watch = &g_array_index(dir->watches,
159 QFileMonitorWatch, i);
160 g_free(watch->filename);
162 g_array_unref(dir->watches);
169 qemu_file_monitor_new(Error **errp)
174 fd = inotify_init1(IN_NONBLOCK);
176 error_setg_errno(errp, errno,
177 "Unable to initialize inotify");
181 mon = g_new0(QFileMonitor, 1);
182 qemu_mutex_init(&mon->lock);
185 mon->dirs = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
186 qemu_file_monitor_dir_free);
187 mon->idmap = g_hash_table_new(g_direct_hash, g_direct_equal);
189 trace_qemu_file_monitor_new(mon, mon->fd);
195 qemu_file_monitor_free_idle(void *opaque)
197 QFileMonitor *mon = opaque;
200 return G_SOURCE_REMOVE;
203 qemu_mutex_lock(&mon->lock);
205 g_hash_table_unref(mon->idmap);
206 g_hash_table_unref(mon->dirs);
208 qemu_mutex_unlock(&mon->lock);
210 qemu_mutex_destroy(&mon->lock);
213 return G_SOURCE_REMOVE;
217 qemu_file_monitor_free(QFileMonitor *mon)
223 qemu_mutex_lock(&mon->lock);
225 qemu_set_fd_handler(mon->fd, NULL, NULL, NULL);
229 qemu_mutex_unlock(&mon->lock);
232 * Can't free it yet, because another thread
233 * may be running event loop, so the inotify
234 * callback might be pending. Using an idle
235 * source ensures we'll only free after the
236 * pending callback is done
238 g_idle_add((GSourceFunc)qemu_file_monitor_free_idle, mon);
242 qemu_file_monitor_add_watch(QFileMonitor *mon,
244 const char *filename,
245 QFileMonitorHandler cb,
249 QFileMonitorDir *dir;
250 QFileMonitorWatch watch;
253 qemu_mutex_lock(&mon->lock);
254 dir = g_hash_table_lookup(mon->dirs, dirpath);
256 int rv = inotify_add_watch(mon->fd, dirpath,
257 IN_CREATE | IN_DELETE | IN_MODIFY |
258 IN_MOVED_TO | IN_MOVED_FROM | IN_ATTRIB);
261 error_setg_errno(errp, errno, "Unable to watch '%s'", dirpath);
265 trace_qemu_file_monitor_enable_watch(mon, dirpath, rv);
267 dir = g_new0(QFileMonitorDir, 1);
268 dir->path = g_strdup(dirpath);
269 dir->inotify_id = rv;
270 dir->watches = g_array_new(FALSE, TRUE, sizeof(QFileMonitorWatch));
272 g_hash_table_insert(mon->dirs, dir->path, dir);
273 g_hash_table_insert(mon->idmap, GINT_TO_POINTER(rv), dir);
275 if (g_hash_table_size(mon->dirs) == 1) {
276 qemu_set_fd_handler(mon->fd, qemu_file_monitor_watch, NULL, mon);
280 watch.id = (((int64_t)dir->inotify_id) << 32) | dir->next_file_id++;
281 watch.filename = g_strdup(filename);
283 watch.opaque = opaque;
285 g_array_append_val(dir->watches, watch);
287 trace_qemu_file_monitor_add_watch(mon, dirpath,
288 filename ? filename : "<none>",
289 cb, opaque, watch.id);
294 qemu_mutex_unlock(&mon->lock);
299 void qemu_file_monitor_remove_watch(QFileMonitor *mon,
303 QFileMonitorDir *dir;
306 qemu_mutex_lock(&mon->lock);
308 trace_qemu_file_monitor_remove_watch(mon, dirpath, id);
310 dir = g_hash_table_lookup(mon->dirs, dirpath);
315 for (i = 0; i < dir->watches->len; i++) {
316 QFileMonitorWatch *watch = &g_array_index(dir->watches,
317 QFileMonitorWatch, i);
318 if (watch->id == id) {
319 g_free(watch->filename);
320 g_array_remove_index(dir->watches, i);
325 if (dir->watches->len == 0) {
326 inotify_rm_watch(mon->fd, dir->inotify_id);
327 trace_qemu_file_monitor_disable_watch(mon, dir->path, dir->inotify_id);
329 g_hash_table_remove(mon->idmap, GINT_TO_POINTER(dir->inotify_id));
330 g_hash_table_remove(mon->dirs, dir->path);
332 if (g_hash_table_size(mon->dirs) == 0) {
333 qemu_set_fd_handler(mon->fd, NULL, NULL, NULL);
338 qemu_mutex_unlock(&mon->lock);