]> git.ipfire.org Git - thirdparty/qemu.git/blame - util/filemonitor-inotify.c
filemon: ensure watch IDs are unique to QFileMonitor scope
[thirdparty/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
9 * version 2 of the License, or (at your option) any later version.
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;
ff3dc8fe 32 int nextid; /* watch ID counter */
90e33dfe
DB
33 QemuMutex lock; /* protects dirs & idmap */
34 GHashTable *dirs; /* dirname => QFileMonitorDir */
35 GHashTable *idmap; /* inotify ID => dirname */
36};
37
38
39typedef struct {
40 int id; /* watch ID */
41 char *filename; /* optional filter */
42 QFileMonitorHandler cb;
43 void *opaque;
44} QFileMonitorWatch;
45
46
47typedef struct {
48 char *path;
49 int id; /* inotify ID */
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) {
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);
92 int qev;
93 gsize i;
94
95 used += sizeof(struct inotify_event) + ev->len;
96
97 if (!dir) {
98 continue;
99 }
100
101 /*
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
106 */
107 switch (iev) {
108 case IN_CREATE:
109 case IN_MOVED_TO:
110 qev = QFILE_MONITOR_EVENT_CREATED;
111 break;
112 case IN_MODIFY:
113 qev = QFILE_MONITOR_EVENT_MODIFIED;
114 break;
115 case IN_DELETE:
116 case IN_MOVED_FROM:
117 qev = QFILE_MONITOR_EVENT_DELETED;
118 break;
119 case IN_ATTRIB:
120 qev = QFILE_MONITOR_EVENT_ATTRIBUTES;
121 break;
122 case IN_IGNORED:
123 qev = QFILE_MONITOR_EVENT_IGNORED;
124 break;
125 default:
126 g_assert_not_reached();
127 }
128
129 trace_qemu_file_monitor_event(mon, dir->path, name, ev->mask, dir->id);
130 for (i = 0; i < dir->watches->len; i++) {
131 QFileMonitorWatch *watch = &g_array_index(dir->watches,
132 QFileMonitorWatch,
133 i);
134
135 if (watch->filename == NULL ||
136 (name && g_str_equal(watch->filename, name))) {
137 trace_qemu_file_monitor_dispatch(mon, dir->path, name,
138 qev, watch->cb,
139 watch->opaque, watch->id);
140 watch->cb(watch->id, qev, name, watch->opaque);
141 }
142 }
143 }
144
145 cleanup:
146 qemu_mutex_unlock(&mon->lock);
147}
148
149
150static void
151qemu_file_monitor_dir_free(void *data)
152{
153 QFileMonitorDir *dir = data;
154 gsize i;
155
156 for (i = 0; i < dir->watches->len; i++) {
157 QFileMonitorWatch *watch = &g_array_index(dir->watches,
158 QFileMonitorWatch, i);
159 g_free(watch->filename);
160 }
161 g_array_unref(dir->watches);
162 g_free(dir->path);
163 g_free(dir);
164}
165
166
167QFileMonitor *
168qemu_file_monitor_new(Error **errp)
169{
170 int fd;
171 QFileMonitor *mon;
172
173 fd = inotify_init1(IN_NONBLOCK);
174 if (fd < 0) {
175 error_setg_errno(errp, errno,
176 "Unable to initialize inotify");
177 return NULL;
178 }
179
180 mon = g_new0(QFileMonitor, 1);
181 qemu_mutex_init(&mon->lock);
182 mon->fd = fd;
183
184 mon->dirs = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
185 qemu_file_monitor_dir_free);
186 mon->idmap = g_hash_table_new(g_direct_hash, g_direct_equal);
187
188 trace_qemu_file_monitor_new(mon, mon->fd);
189
190 return mon;
191}
192
193static gboolean
194qemu_file_monitor_free_idle(void *opaque)
195{
196 QFileMonitor *mon = opaque;
197
198 if (!mon) {
199 return G_SOURCE_REMOVE;
200 }
201
202 qemu_mutex_lock(&mon->lock);
203
204 g_hash_table_unref(mon->idmap);
205 g_hash_table_unref(mon->dirs);
206
207 qemu_mutex_unlock(&mon->lock);
208
209 qemu_mutex_destroy(&mon->lock);
210 g_free(mon);
211
212 return G_SOURCE_REMOVE;
213}
214
215void
216qemu_file_monitor_free(QFileMonitor *mon)
217{
218 if (!mon) {
219 return;
220 }
221
222 qemu_mutex_lock(&mon->lock);
223 if (mon->fd != -1) {
224 qemu_set_fd_handler(mon->fd, NULL, NULL, NULL);
225 close(mon->fd);
226 mon->fd = -1;
227 }
228 qemu_mutex_unlock(&mon->lock);
229
230 /*
231 * Can't free it yet, because another thread
232 * may be running event loop, so the inotify
233 * callback might be pending. Using an idle
234 * source ensures we'll only free after the
235 * pending callback is done
236 */
237 g_idle_add((GSourceFunc)qemu_file_monitor_free_idle, mon);
238}
239
240int
241qemu_file_monitor_add_watch(QFileMonitor *mon,
242 const char *dirpath,
243 const char *filename,
244 QFileMonitorHandler cb,
245 void *opaque,
246 Error **errp)
247{
248 QFileMonitorDir *dir;
249 QFileMonitorWatch watch;
250 int ret = -1;
251
252 qemu_mutex_lock(&mon->lock);
253 dir = g_hash_table_lookup(mon->dirs, dirpath);
254 if (!dir) {
255 int rv = inotify_add_watch(mon->fd, dirpath,
256 IN_CREATE | IN_DELETE | IN_MODIFY |
257 IN_MOVED_TO | IN_MOVED_FROM | IN_ATTRIB);
258
259 if (rv < 0) {
260 error_setg_errno(errp, errno, "Unable to watch '%s'", dirpath);
261 goto cleanup;
262 }
263
264 trace_qemu_file_monitor_enable_watch(mon, dirpath, rv);
265
266 dir = g_new0(QFileMonitorDir, 1);
267 dir->path = g_strdup(dirpath);
268 dir->id = rv;
269 dir->watches = g_array_new(FALSE, TRUE, sizeof(QFileMonitorWatch));
270
271 g_hash_table_insert(mon->dirs, dir->path, dir);
272 g_hash_table_insert(mon->idmap, GINT_TO_POINTER(rv), dir);
273
274 if (g_hash_table_size(mon->dirs) == 1) {
275 qemu_set_fd_handler(mon->fd, qemu_file_monitor_watch, NULL, mon);
276 }
277 }
278
ff3dc8fe 279 watch.id = mon->nextid++;
90e33dfe
DB
280 watch.filename = g_strdup(filename);
281 watch.cb = cb;
282 watch.opaque = opaque;
283
284 g_array_append_val(dir->watches, watch);
285
286 trace_qemu_file_monitor_add_watch(mon, dirpath,
287 filename ? filename : "<none>",
288 cb, opaque, watch.id);
289
290 ret = watch.id;
291
292 cleanup:
293 qemu_mutex_unlock(&mon->lock);
294 return ret;
295}
296
297
298void qemu_file_monitor_remove_watch(QFileMonitor *mon,
299 const char *dirpath,
300 int id)
301{
302 QFileMonitorDir *dir;
303 gsize i;
304
305 qemu_mutex_lock(&mon->lock);
306
307 trace_qemu_file_monitor_remove_watch(mon, dirpath, id);
308
309 dir = g_hash_table_lookup(mon->dirs, dirpath);
310 if (!dir) {
311 goto cleanup;
312 }
313
314 for (i = 0; i < dir->watches->len; i++) {
315 QFileMonitorWatch *watch = &g_array_index(dir->watches,
316 QFileMonitorWatch, i);
317 if (watch->id == id) {
318 g_free(watch->filename);
319 g_array_remove_index(dir->watches, i);
320 break;
321 }
322 }
323
324 if (dir->watches->len == 0) {
325 inotify_rm_watch(mon->fd, dir->id);
326 trace_qemu_file_monitor_disable_watch(mon, dir->path, dir->id);
327
328 g_hash_table_remove(mon->idmap, GINT_TO_POINTER(dir->id));
329 g_hash_table_remove(mon->dirs, dir->path);
330
331 if (g_hash_table_size(mon->dirs) == 0) {
332 qemu_set_fd_handler(mon->fd, NULL, NULL, NULL);
333 }
334 }
335
336 cleanup:
337 qemu_mutex_unlock(&mon->lock);
338}