OSDN Git Service

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