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 | 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 | 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 | * During a rename operation, the old name gets |
103 | * IN_MOVED_FROM and the new name gets IN_MOVED_TO. |
104 | * To simplify life for callers, we turn these into |
105 | * DELETED and CREATED events |
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 | |
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 | int64_t |
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 | 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 | |
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_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 | |