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