cachepc-qemu

Fork of AMDESE/qemu with changes for cachepc side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-qemu
Log | Files | Refs | Submodules | LICENSE | sfeed.txt

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}