1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
32 QemuMutex lock;
33 GHashTable *dirs;
34 GHashTable *idmap;
35};
36
37
38typedef struct {
39 int64_t id;
40 char *filename;
41 QFileMonitorHandler cb;
42 void *opaque;
43} QFileMonitorWatch;
44
45
46typedef struct {
47 char *path;
48 int inotify_id;
49 int next_file_id;
50 GArray *watches;
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
79 goto cleanup;
80 }
81
82
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
103
104
105
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,
130 dir->inotify_id);
131 for (i = 0; i < dir->watches->len; i++) {
132 QFileMonitorWatch *watch = &g_array_index(dir->watches,
133 QFileMonitorWatch,
134 i);
135
136 if (watch->filename == NULL ||
137 (name && g_str_equal(watch->filename, name))) {
138 trace_qemu_file_monitor_dispatch(mon, dir->path, name,
139 qev, watch->cb,
140 watch->opaque, watch->id);
141 watch->cb(watch->id, qev, name, watch->opaque);
142 }
143 }
144 }
145
146 cleanup:
147 qemu_mutex_unlock(&mon->lock);
148}
149
150
151static void
152qemu_file_monitor_dir_free(void *data)
153{
154 QFileMonitorDir *dir = data;
155 gsize i;
156
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);
161 }
162 g_array_unref(dir->watches);
163 g_free(dir->path);
164 g_free(dir);
165}
166
167
168QFileMonitor *
169qemu_file_monitor_new(Error **errp)
170{
171 int fd;
172 QFileMonitor *mon;
173
174 fd = inotify_init1(IN_NONBLOCK);
175 if (fd < 0) {
176 error_setg_errno(errp, errno,
177 "Unable to initialize inotify");
178 return NULL;
179 }
180
181 mon = g_new0(QFileMonitor, 1);
182 qemu_mutex_init(&mon->lock);
183 mon->fd = fd;
184
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);
188
189 trace_qemu_file_monitor_new(mon, mon->fd);
190
191 return mon;
192}
193
194static gboolean
195qemu_file_monitor_free_idle(void *opaque)
196{
197 QFileMonitor *mon = opaque;
198
199 if (!mon) {
200 return G_SOURCE_REMOVE;
201 }
202
203 qemu_mutex_lock(&mon->lock);
204
205 g_hash_table_unref(mon->idmap);
206 g_hash_table_unref(mon->dirs);
207
208 qemu_mutex_unlock(&mon->lock);
209
210 qemu_mutex_destroy(&mon->lock);
211 g_free(mon);
212
213 return G_SOURCE_REMOVE;
214}
215
216void
217qemu_file_monitor_free(QFileMonitor *mon)
218{
219 if (!mon) {
220 return;
221 }
222
223 qemu_mutex_lock(&mon->lock);
224 if (mon->fd != -1) {
225 qemu_set_fd_handler(mon->fd, NULL, NULL, NULL);
226 close(mon->fd);
227 mon->fd = -1;
228 }
229 qemu_mutex_unlock(&mon->lock);
230
231
232
233
234
235
236
237
238 g_idle_add((GSourceFunc)qemu_file_monitor_free_idle, mon);
239}
240
241int64_t
242qemu_file_monitor_add_watch(QFileMonitor *mon,
243 const char *dirpath,
244 const char *filename,
245 QFileMonitorHandler cb,
246 void *opaque,
247 Error **errp)
248{
249 QFileMonitorDir *dir;
250 QFileMonitorWatch watch;
251 int64_t ret = -1;
252
253 qemu_mutex_lock(&mon->lock);
254 dir = g_hash_table_lookup(mon->dirs, dirpath);
255 if (!dir) {
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);
259
260 if (rv < 0) {
261 error_setg_errno(errp, errno, "Unable to watch '%s'", dirpath);
262 goto cleanup;
263 }
264
265 trace_qemu_file_monitor_enable_watch(mon, dirpath, rv);
266
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));
271
272 g_hash_table_insert(mon->dirs, dir->path, dir);
273 g_hash_table_insert(mon->idmap, GINT_TO_POINTER(rv), dir);
274
275 if (g_hash_table_size(mon->dirs) == 1) {
276 qemu_set_fd_handler(mon->fd, qemu_file_monitor_watch, NULL, mon);
277 }
278 }
279
280 watch.id = (((int64_t)dir->inotify_id) << 32) | dir->next_file_id++;
281 watch.filename = g_strdup(filename);
282 watch.cb = cb;
283 watch.opaque = opaque;
284
285 g_array_append_val(dir->watches, watch);
286
287 trace_qemu_file_monitor_add_watch(mon, dirpath,
288 filename ? filename : "<none>",
289 cb, opaque, watch.id);
290
291 ret = watch.id;
292
293 cleanup:
294 qemu_mutex_unlock(&mon->lock);
295 return ret;
296}
297
298
299void qemu_file_monitor_remove_watch(QFileMonitor *mon,
300 const char *dirpath,
301 int64_t id)
302{
303 QFileMonitorDir *dir;
304 gsize i;
305
306 qemu_mutex_lock(&mon->lock);
307
308 trace_qemu_file_monitor_remove_watch(mon, dirpath, id);
309
310 dir = g_hash_table_lookup(mon->dirs, dirpath);
311 if (!dir) {
312 goto cleanup;
313 }
314
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);
321 break;
322 }
323 }
324
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);
328
329 g_hash_table_remove(mon->idmap, GINT_TO_POINTER(dir->inotify_id));
330 g_hash_table_remove(mon->dirs, dir->path);
331
332 if (g_hash_table_size(mon->dirs) == 0) {
333 qemu_set_fd_handler(mon->fd, NULL, NULL, NULL);
334 }
335 }
336
337 cleanup:
338 qemu_mutex_unlock(&mon->lock);
339}
340