master
c 336 lines 9.22 KB
Raw
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.1 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/lockable.h"
25 #include "qemu/error-report.h"
26 #include "qapi/error.h"
27 #include "trace.h"
28
29 #include <sys/inotify.h>
30
31 struct QFileMonitor {
32 int fd;
33 QemuMutex lock; /* protects dirs & idmap */
34 GHashTable *dirs; /* dirname => QFileMonitorDir */
35 GHashTable *idmap; /* inotify ID => dirname */
36 };
37
38
39 typedef struct {
40 int64_t id; /* watch ID */
41 char *filename; /* optional filter */
42 QFileMonitorHandler cb;
43 void *opaque;
44 } QFileMonitorWatch;
45
46
47 typedef struct {
48 char *path;
49 int inotify_id; /* inotify ID */
50 int next_file_id; /* file ID counter */
51 GArray *watches; /* QFileMonitorWatch elements */
52 } QFileMonitorDir;
53
54
55 static void qemu_file_monitor_watch(void *arg)
56 {
57 QFileMonitor *mon = arg;
58 char buf[4096]
59 __attribute__ ((aligned(__alignof__(struct inotify_event))));
60 int used = 0;
61 int len;
62
63 QEMU_LOCK_GUARD(&mon->lock);
64
65 if (mon->fd == -1) {
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 }
76
77 /* no more events right now */
78 return;
79 }
80
81 /* Loop over all events in the buffer */
82 while (used < len) {
83 const char *name;
84 QFileMonitorDir *dir;
85 uint32_t iev;
86 int qev;
87 gsize i;
88 struct inotify_event *ev = (struct inotify_event *)(buf + used);
89
90 /*
91 * We trust the kernel to provide valid buffer with complete event
92 * records.
93 */
94 assert(len - used >= sizeof(struct inotify_event));
95 assert(len - used - sizeof(struct inotify_event) >= ev->len);
96
97 name = ev->len ? ev->name : "";
98 dir = g_hash_table_lookup(mon->idmap, GINT_TO_POINTER(ev->wd));
99 iev = ev->mask &
100 (IN_CREATE | IN_MODIFY | IN_DELETE | IN_IGNORED |
101 IN_MOVED_TO | IN_MOVED_FROM | IN_ATTRIB);
102
103 used += sizeof(struct inotify_event) + ev->len;
104
105 if (!dir) {
106 continue;
107 }
108
109 /*
110 * During a rename operation, the old name gets
111 * IN_MOVED_FROM and the new name gets IN_MOVED_TO.
112 * To simplify life for callers, we turn these into
113 * DELETED and CREATED events
114 */
115 switch (iev) {
116 case IN_CREATE:
117 case IN_MOVED_TO:
118 qev = QFILE_MONITOR_EVENT_CREATED;
119 break;
120 case IN_MODIFY:
121 qev = QFILE_MONITOR_EVENT_MODIFIED;
122 break;
123 case IN_DELETE:
124 case IN_MOVED_FROM:
125 qev = QFILE_MONITOR_EVENT_DELETED;
126 break;
127 case IN_ATTRIB:
128 qev = QFILE_MONITOR_EVENT_ATTRIBUTES;
129 break;
130 case IN_IGNORED:
131 qev = QFILE_MONITOR_EVENT_IGNORED;
132 break;
133 default:
134 g_assert_not_reached();
135 }
136
137 trace_qemu_file_monitor_event(mon, dir->path, name, ev->mask,
138 dir->inotify_id);
139 for (i = 0; i < dir->watches->len; i++) {
140 QFileMonitorWatch *watch = &g_array_index(dir->watches,
141 QFileMonitorWatch,
142 i);
143
144 if (watch->filename == NULL ||
145 (name && g_str_equal(watch->filename, name))) {
146 trace_qemu_file_monitor_dispatch(mon, dir->path, name,
147 qev, watch->cb,
148 watch->opaque, watch->id);
149 watch->cb(watch->id, qev, name, watch->opaque);
150 }
151 }
152 }
153 }
154
155
156 static void
157 qemu_file_monitor_dir_free(void *data)
158 {
159 QFileMonitorDir *dir = data;
160 gsize i;
161
162 for (i = 0; i < dir->watches->len; i++) {
163 QFileMonitorWatch *watch = &g_array_index(dir->watches,
164 QFileMonitorWatch, i);
165 g_free(watch->filename);
166 }
167 g_array_unref(dir->watches);
168 g_free(dir->path);
169 g_free(dir);
170 }
171
172
173 QFileMonitor *
174 qemu_file_monitor_new(Error **errp)
175 {
176 int fd;
177 QFileMonitor *mon;
178
179 fd = inotify_init1(IN_NONBLOCK);
180 if (fd < 0) {
181 error_setg_errno(errp, errno,
182 "Unable to initialize inotify");
183 return NULL;
184 }
185
186 mon = g_new0(QFileMonitor, 1);
187 qemu_mutex_init(&mon->lock);
188 mon->fd = fd;
189
190 mon->dirs = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
191 qemu_file_monitor_dir_free);
192 mon->idmap = g_hash_table_new(g_direct_hash, g_direct_equal);
193
194 trace_qemu_file_monitor_new(mon, mon->fd);
195
196 return mon;
197 }
198
199 static gboolean
200 qemu_file_monitor_free_idle(void *opaque)
201 {
202 QFileMonitor *mon = opaque;
203
204 if (!mon) {
205 return G_SOURCE_REMOVE;
206 }
207
208 qemu_mutex_lock(&mon->lock);
209
210 g_hash_table_unref(mon->idmap);
211 g_hash_table_unref(mon->dirs);
212
213 qemu_mutex_unlock(&mon->lock);
214
215 qemu_mutex_destroy(&mon->lock);
216 g_free(mon);
217
218 return G_SOURCE_REMOVE;
219 }
220
221 void
222 qemu_file_monitor_free(QFileMonitor *mon)
223 {
224 if (!mon) {
225 return;
226 }
227
228 qemu_mutex_lock(&mon->lock);
229 if (mon->fd != -1) {
230 qemu_set_fd_handler(mon->fd, NULL, NULL, NULL);
231 close(mon->fd);
232 mon->fd = -1;
233 }
234 qemu_mutex_unlock(&mon->lock);
235
236 /*
237 * Can't free it yet, because another thread
238 * may be running event loop, so the inotify
239 * callback might be pending. Using an idle
240 * source ensures we'll only free after the
241 * pending callback is done
242 */
243 g_idle_add((GSourceFunc)qemu_file_monitor_free_idle, mon);
244 }
245
246 int64_t
247 qemu_file_monitor_add_watch(QFileMonitor *mon,
248 const char *dirpath,
249 const char *filename,
250 QFileMonitorHandler cb,
251 void *opaque,
252 Error **errp)
253 {
254 QFileMonitorDir *dir;
255 QFileMonitorWatch watch;
256
257 QEMU_LOCK_GUARD(&mon->lock);
258 dir = g_hash_table_lookup(mon->dirs, dirpath);
259 if (!dir) {
260 int rv = inotify_add_watch(mon->fd, dirpath,
261 IN_CREATE | IN_DELETE | IN_MODIFY |
262 IN_MOVED_TO | IN_MOVED_FROM | IN_ATTRIB);
263
264 if (rv < 0) {
265 error_setg_errno(errp, errno, "Unable to watch '%s'", dirpath);
266 return -1;
267 }
268
269 trace_qemu_file_monitor_enable_watch(mon, dirpath, rv);
270
271 dir = g_new0(QFileMonitorDir, 1);
272 dir->path = g_strdup(dirpath);
273 dir->inotify_id = rv;
274 dir->watches = g_array_new(FALSE, TRUE, sizeof(QFileMonitorWatch));
275
276 g_hash_table_insert(mon->dirs, dir->path, dir);
277 g_hash_table_insert(mon->idmap, GINT_TO_POINTER(rv), dir);
278
279 if (g_hash_table_size(mon->dirs) == 1) {
280 qemu_set_fd_handler(mon->fd, qemu_file_monitor_watch, NULL, mon);
281 }
282 }
283
284 watch.id = (((int64_t)dir->inotify_id) << 32) | dir->next_file_id++;
285 watch.filename = g_strdup(filename);
286 watch.cb = cb;
287 watch.opaque = opaque;
288
289 g_array_append_val(dir->watches, watch);
290
291 trace_qemu_file_monitor_add_watch(mon, dirpath,
292 filename ? filename : "<none>",
293 cb, opaque, watch.id);
294
295 return watch.id;
296 }
297
298
299 void 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_LOCK_GUARD(&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 return;
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 }