qemu/util/filemonitor-inotify.c
<<
>>
Prefs
   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}
 340