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 kenel 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 } 349