xref: /openbmc/qemu/util/filemonitor-inotify.c (revision 709395f8)
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 
30 struct QFileMonitor {
31     int fd;
32 
33     QemuMutex lock; /* protects dirs & idmap */
34     GHashTable *dirs; /* dirname => QFileMonitorDir */
35     GHashTable *idmap; /* inotify ID => dirname */
36 };
37 
38 
39 typedef struct {
40     int 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 id; /* inotify ID */
50     int nextid; /* watch 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_mutex_lock(&mon->lock);
64 
65     if (mon->fd == -1) {
66         qemu_mutex_unlock(&mon->lock);
67         return;
68     }
69 
70     len = read(mon->fd, buf, sizeof(buf));
71 
72     if (len < 0) {
73         if (errno != EAGAIN) {
74             error_report("Failure monitoring inotify FD '%s',"
75                          "disabling events", strerror(errno));
76             goto cleanup;
77         }
78 
79         /* no more events right now */
80         goto cleanup;
81     }
82 
83     /* Loop over all events in the buffer */
84     while (used < len) {
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);
93         int qev;
94         gsize i;
95 
96         used += sizeof(struct inotify_event) + ev->len;
97 
98         if (!dir) {
99             continue;
100         }
101 
102         /*
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
107          */
108         switch (iev) {
109         case IN_CREATE:
110         case IN_MOVED_TO:
111             qev = QFILE_MONITOR_EVENT_CREATED;
112             break;
113         case IN_MODIFY:
114             qev = QFILE_MONITOR_EVENT_MODIFIED;
115             break;
116         case IN_DELETE:
117         case IN_MOVED_FROM:
118             qev = QFILE_MONITOR_EVENT_DELETED;
119             break;
120         case IN_ATTRIB:
121             qev = QFILE_MONITOR_EVENT_ATTRIBUTES;
122             break;
123         case IN_IGNORED:
124             qev = QFILE_MONITOR_EVENT_IGNORED;
125             break;
126         default:
127             g_assert_not_reached();
128         }
129 
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,
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 
151 static void
152 qemu_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 
168 QFileMonitor *
169 qemu_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 
194 static gboolean
195 qemu_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 
216 void
217 qemu_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      * 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
237      */
238     g_idle_add((GSourceFunc)qemu_file_monitor_free_idle, mon);
239 }
240 
241 int
242 qemu_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     int 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->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 = dir->nextid++;
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 
299 void qemu_file_monitor_remove_watch(QFileMonitor *mon,
300                                     const char *dirpath,
301                                     int 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->id);
327         trace_qemu_file_monitor_disable_watch(mon, dir->path, dir->id);
328 
329         g_hash_table_remove(mon->idmap, GINT_TO_POINTER(dir->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