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 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>
33 QemuMutex lock
; /* protects dirs & idmap */
34 GHashTable
*dirs
; /* dirname => QFileMonitorDir */
35 GHashTable
*idmap
; /* inotify ID => dirname */
40 int id
; /* watch ID */
41 char *filename
; /* optional filter */
42 QFileMonitorHandler cb
;
49 int id
; /* inotify ID */
50 int nextid
; /* watch ID counter */
51 GArray
*watches
; /* QFileMonitorWatch elements */
55 static void qemu_file_monitor_watch(void *arg
)
57 QFileMonitor
*mon
= arg
;
59 __attribute__ ((aligned(__alignof__(struct inotify_event
))));
63 qemu_mutex_lock(&mon
->lock
);
66 qemu_mutex_unlock(&mon
->lock
);
70 len
= read(mon
->fd
, buf
, sizeof(buf
));
73 if (errno
!= EAGAIN
) {
74 error_report("Failure monitoring inotify FD '%s',"
75 "disabling events", strerror(errno
));
79 /* no more events right now */
83 /* Loop over all events in the buffer */
85 struct inotify_event
*ev
=
86 (struct inotify_event
*)(buf
+ used
);
87 const char *name
= ev
->len
? ev
->name
: "";
88 QFileMonitorDir
*dir
= g_hash_table_lookup(mon
->idmap
,
89 GINT_TO_POINTER(ev
->wd
));
90 uint32_t iev
= ev
->mask
&
91 (IN_CREATE
| IN_MODIFY
| IN_DELETE
| IN_IGNORED
|
92 IN_MOVED_TO
| IN_MOVED_FROM
| IN_ATTRIB
);
96 used
+= sizeof(struct inotify_event
) + ev
->len
;
103 * During a rename operation, the old name gets
104 * IN_MOVED_FROM and the new name gets IN_MOVED_TO.
105 * To simplify life for callers, we turn these into
106 * DELETED and CREATED events
111 qev
= QFILE_MONITOR_EVENT_CREATED
;
114 qev
= QFILE_MONITOR_EVENT_MODIFIED
;
118 qev
= QFILE_MONITOR_EVENT_DELETED
;
121 qev
= QFILE_MONITOR_EVENT_ATTRIBUTES
;
124 qev
= QFILE_MONITOR_EVENT_IGNORED
;
127 g_assert_not_reached();
130 trace_qemu_file_monitor_event(mon
, dir
->path
, name
, ev
->mask
, dir
->id
);
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
);
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
= dir
->nextid
++;
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
->id
);
327 trace_qemu_file_monitor_disable_watch(mon
, dir
->path
, dir
->id
);
329 g_hash_table_remove(mon
->idmap
, GINT_TO_POINTER(dir
->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
);