]> git.proxmox.com Git - mirror_qemu.git/blame - util/filemonitor-inotify.c
iotests: add filter_qmp_generated_node_ids()
[mirror_qemu.git] / util / filemonitor-inotify.c
CommitLineData
90e33dfe
DB
1/*
2 * QEMU file monitor Linux inotify impl
3 *
4 * Copyright (c) 2018 Red Hat, Inc.
5 *
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
61f3c91a 9 * version 2.1 of the License, or (at your option) any later version.
90e33dfe
DB
10 *
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.
15 *
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/>.
18 *
19 */
20
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"
26#include "trace.h"
27
28#include <sys/inotify.h>
29
30struct QFileMonitor {
31 int fd;
90e33dfe
DB
32 QemuMutex lock; /* protects dirs & idmap */
33 GHashTable *dirs; /* dirname => QFileMonitorDir */
34 GHashTable *idmap; /* inotify ID => dirname */
35};
36
37
38typedef struct {
b4682a63 39 int64_t id; /* watch ID */
90e33dfe
DB
40 char *filename; /* optional filter */
41 QFileMonitorHandler cb;
42 void *opaque;
43} QFileMonitorWatch;
44
45
46typedef struct {
47 char *path;
b4682a63
DB
48 int inotify_id; /* inotify ID */
49 int next_file_id; /* file ID counter */
90e33dfe
DB
50 GArray *watches; /* QFileMonitorWatch elements */
51} QFileMonitorDir;
52
53
54static void qemu_file_monitor_watch(void *arg)
55{
56 QFileMonitor *mon = arg;
57 char buf[4096]
58 __attribute__ ((aligned(__alignof__(struct inotify_event))));
59 int used = 0;
60 int len;
61
62 qemu_mutex_lock(&mon->lock);
63
64 if (mon->fd == -1) {
65 qemu_mutex_unlock(&mon->lock);
66 return;
67 }
68
69 len = read(mon->fd, buf, sizeof(buf));
70
71 if (len < 0) {
72 if (errno != EAGAIN) {
73 error_report("Failure monitoring inotify FD '%s',"
74 "disabling events", strerror(errno));
75 goto cleanup;
76 }
77
78 /* no more events right now */
79 goto cleanup;
80 }
81
82 /* Loop over all events in the buffer */
83 while (used < len) {
2e12dd40
VSO
84 const char *name;
85 QFileMonitorDir *dir;
86 uint32_t iev;
90e33dfe
DB
87 int qev;
88 gsize i;
2e12dd40
VSO
89 struct inotify_event *ev = (struct inotify_event *)(buf + used);
90
91 /*
f0dbe427 92 * We trust the kernel to provide valid buffer with complete event
2e12dd40
VSO
93 * records.
94 */
95 assert(len - used >= sizeof(struct inotify_event));
96 assert(len - used - sizeof(struct inotify_event) >= ev->len);
97
98 name = ev->len ? ev->name : "";
99 dir = g_hash_table_lookup(mon->idmap, GINT_TO_POINTER(ev->wd));
100 iev = ev->mask &
101 (IN_CREATE | IN_MODIFY | IN_DELETE | IN_IGNORED |
102 IN_MOVED_TO | IN_MOVED_FROM | IN_ATTRIB);
90e33dfe
DB
103
104 used += sizeof(struct inotify_event) + ev->len;
105
106 if (!dir) {
107 continue;
108 }
109
110 /*
111 * During a rename operation, the old name gets
112 * IN_MOVED_FROM and the new name gets IN_MOVED_TO.
113 * To simplify life for callers, we turn these into
114 * DELETED and CREATED events
115 */
116 switch (iev) {
117 case IN_CREATE:
118 case IN_MOVED_TO:
119 qev = QFILE_MONITOR_EVENT_CREATED;
120 break;
121 case IN_MODIFY:
122 qev = QFILE_MONITOR_EVENT_MODIFIED;
123 break;
124 case IN_DELETE:
125 case IN_MOVED_FROM:
126 qev = QFILE_MONITOR_EVENT_DELETED;
127 break;
128 case IN_ATTRIB:
129 qev = QFILE_MONITOR_EVENT_ATTRIBUTES;
130 break;
131 case IN_IGNORED:
132 qev = QFILE_MONITOR_EVENT_IGNORED;
133 break;
134 default:
135 g_assert_not_reached();
136 }
137
b4682a63
DB
138 trace_qemu_file_monitor_event(mon, dir->path, name, ev->mask,
139 dir->inotify_id);
90e33dfe
DB
140 for (i = 0; i < dir->watches->len; i++) {
141 QFileMonitorWatch *watch = &g_array_index(dir->watches,
142 QFileMonitorWatch,
143 i);
144
145 if (watch->filename == NULL ||
146 (name && g_str_equal(watch->filename, name))) {
147 trace_qemu_file_monitor_dispatch(mon, dir->path, name,
148 qev, watch->cb,
149 watch->opaque, watch->id);
150 watch->cb(watch->id, qev, name, watch->opaque);
151 }
152 }
153 }
154
155 cleanup:
156 qemu_mutex_unlock(&mon->lock);
157}
158
159
160static void
161qemu_file_monitor_dir_free(void *data)
162{
163 QFileMonitorDir *dir = data;
164 gsize i;
165
166 for (i = 0; i < dir->watches->len; i++) {
167 QFileMonitorWatch *watch = &g_array_index(dir->watches,
168 QFileMonitorWatch, i);
169 g_free(watch->filename);
170 }
171 g_array_unref(dir->watches);
172 g_free(dir->path);
173 g_free(dir);
174}
175
176
177QFileMonitor *
178qemu_file_monitor_new(Error **errp)
179{
180 int fd;
181 QFileMonitor *mon;
182
183 fd = inotify_init1(IN_NONBLOCK);
184 if (fd < 0) {
185 error_setg_errno(errp, errno,
186 "Unable to initialize inotify");
187 return NULL;
188 }
189
190 mon = g_new0(QFileMonitor, 1);
191 qemu_mutex_init(&mon->lock);
192 mon->fd = fd;
193
194 mon->dirs = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
195 qemu_file_monitor_dir_free);
196 mon->idmap = g_hash_table_new(g_direct_hash, g_direct_equal);
197
198 trace_qemu_file_monitor_new(mon, mon->fd);
199
200 return mon;
201}
202
203static gboolean
204qemu_file_monitor_free_idle(void *opaque)
205{
206 QFileMonitor *mon = opaque;
207
208 if (!mon) {
209 return G_SOURCE_REMOVE;
210 }
211
212 qemu_mutex_lock(&mon->lock);
213
214 g_hash_table_unref(mon->idmap);
215 g_hash_table_unref(mon->dirs);
216
217 qemu_mutex_unlock(&mon->lock);
218
219 qemu_mutex_destroy(&mon->lock);
220 g_free(mon);
221
222 return G_SOURCE_REMOVE;
223}
224
225void
226qemu_file_monitor_free(QFileMonitor *mon)
227{
228 if (!mon) {
229 return;
230 }
231
232 qemu_mutex_lock(&mon->lock);
233 if (mon->fd != -1) {
234 qemu_set_fd_handler(mon->fd, NULL, NULL, NULL);
235 close(mon->fd);
236 mon->fd = -1;
237 }
238 qemu_mutex_unlock(&mon->lock);
239
240 /*
241 * Can't free it yet, because another thread
242 * may be running event loop, so the inotify
243 * callback might be pending. Using an idle
244 * source ensures we'll only free after the
245 * pending callback is done
246 */
247 g_idle_add((GSourceFunc)qemu_file_monitor_free_idle, mon);
248}
249
b4682a63 250int64_t
90e33dfe
DB
251qemu_file_monitor_add_watch(QFileMonitor *mon,
252 const char *dirpath,
253 const char *filename,
254 QFileMonitorHandler cb,
255 void *opaque,
256 Error **errp)
257{
258 QFileMonitorDir *dir;
259 QFileMonitorWatch watch;
b4682a63 260 int64_t ret = -1;
90e33dfe
DB
261
262 qemu_mutex_lock(&mon->lock);
263 dir = g_hash_table_lookup(mon->dirs, dirpath);
264 if (!dir) {
265 int rv = inotify_add_watch(mon->fd, dirpath,
266 IN_CREATE | IN_DELETE | IN_MODIFY |
267 IN_MOVED_TO | IN_MOVED_FROM | IN_ATTRIB);
268
269 if (rv < 0) {
270 error_setg_errno(errp, errno, "Unable to watch '%s'", dirpath);
271 goto cleanup;
272 }
273
274 trace_qemu_file_monitor_enable_watch(mon, dirpath, rv);
275
276 dir = g_new0(QFileMonitorDir, 1);
277 dir->path = g_strdup(dirpath);
b4682a63 278 dir->inotify_id = rv;
90e33dfe
DB
279 dir->watches = g_array_new(FALSE, TRUE, sizeof(QFileMonitorWatch));
280
281 g_hash_table_insert(mon->dirs, dir->path, dir);
282 g_hash_table_insert(mon->idmap, GINT_TO_POINTER(rv), dir);
283
284 if (g_hash_table_size(mon->dirs) == 1) {
285 qemu_set_fd_handler(mon->fd, qemu_file_monitor_watch, NULL, mon);
286 }
287 }
288
b4682a63 289 watch.id = (((int64_t)dir->inotify_id) << 32) | dir->next_file_id++;
90e33dfe
DB
290 watch.filename = g_strdup(filename);
291 watch.cb = cb;
292 watch.opaque = opaque;
293
294 g_array_append_val(dir->watches, watch);
295
296 trace_qemu_file_monitor_add_watch(mon, dirpath,
297 filename ? filename : "<none>",
298 cb, opaque, watch.id);
299
300 ret = watch.id;
301
302 cleanup:
303 qemu_mutex_unlock(&mon->lock);
304 return ret;
305}
306
307
308void qemu_file_monitor_remove_watch(QFileMonitor *mon,
309 const char *dirpath,
b4682a63 310 int64_t id)
90e33dfe
DB
311{
312 QFileMonitorDir *dir;
313 gsize i;
314
315 qemu_mutex_lock(&mon->lock);
316
317 trace_qemu_file_monitor_remove_watch(mon, dirpath, id);
318
319 dir = g_hash_table_lookup(mon->dirs, dirpath);
320 if (!dir) {
321 goto cleanup;
322 }
323
324 for (i = 0; i < dir->watches->len; i++) {
325 QFileMonitorWatch *watch = &g_array_index(dir->watches,
326 QFileMonitorWatch, i);
327 if (watch->id == id) {
328 g_free(watch->filename);
329 g_array_remove_index(dir->watches, i);
330 break;
331 }
332 }
333
334 if (dir->watches->len == 0) {
b4682a63
DB
335 inotify_rm_watch(mon->fd, dir->inotify_id);
336 trace_qemu_file_monitor_disable_watch(mon, dir->path, dir->inotify_id);
90e33dfe 337
b4682a63 338 g_hash_table_remove(mon->idmap, GINT_TO_POINTER(dir->inotify_id));
90e33dfe
DB
339 g_hash_table_remove(mon->dirs, dir->path);
340
341 if (g_hash_table_size(mon->dirs) == 0) {
342 qemu_set_fd_handler(mon->fd, NULL, NULL, NULL);
343 }
344 }
345
346 cleanup:
347 qemu_mutex_unlock(&mon->lock);
348}