filemonitor-inotify.c (9444B)
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 30struct QFileMonitor { 31 int fd; 32 QemuMutex lock; /* protects dirs & idmap */ 33 GHashTable *dirs; /* dirname => QFileMonitorDir */ 34 GHashTable *idmap; /* inotify ID => dirname */ 35}; 36 37 38typedef struct { 39 int64_t id; /* watch ID */ 40 char *filename; /* optional filter */ 41 QFileMonitorHandler cb; 42 void *opaque; 43} QFileMonitorWatch; 44 45 46typedef 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 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 /* 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 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 * 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 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}