diff mbox series

[v4,01/17] util: add helper APIs for dealing with inotify in portable manner

Message ID 20190215171345.30280-2-berrange@redhat.com (mailing list archive)
State New, archived
Headers show
Series Add authorization support to all network services | expand

Commit Message

Daniel P. Berrangé Feb. 15, 2019, 5:13 p.m. UTC
The inotify userspace API for reading events is quite horrible, so it is
useful to wrap it in a more friendly API to avoid duplicating code
across many users in QEMU. Wrapping it also allows introduction of a
platform portability layer, so that we can add impls for non-Linux based
equivalents in future.

Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 MAINTAINERS                   |   7 +
 Makefile.objs                 |   2 +-
 include/qemu/filemonitor.h    | 128 +++++++
 tests/Makefile.include        |   3 +
 tests/test-util-filemonitor.c | 685 ++++++++++++++++++++++++++++++++++
 util/Makefile.objs            |   3 +
 util/filemonitor-inotify.c    | 338 +++++++++++++++++
 util/filemonitor-stub.c       |  59 +++
 util/trace-events             |   9 +
 9 files changed, 1233 insertions(+), 1 deletion(-)
 create mode 100644 include/qemu/filemonitor.h
 create mode 100644 tests/test-util-filemonitor.c
 create mode 100644 util/filemonitor-inotify.c
 create mode 100644 util/filemonitor-stub.c

Comments

Marc-André Lureau Feb. 16, 2019, 11:27 a.m. UTC | #1
Hi

On Fri, Feb 15, 2019 at 6:13 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
>
> The inotify userspace API for reading events is quite horrible, so it is
> useful to wrap it in a more friendly API to avoid duplicating code
> across many users in QEMU. Wrapping it also allows introduction of a
> platform portability layer, so that we can add impls for non-Linux based
> equivalents in future.
>
> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>

> ---
>  MAINTAINERS                   |   7 +
>  Makefile.objs                 |   2 +-
>  include/qemu/filemonitor.h    | 128 +++++++
>  tests/Makefile.include        |   3 +
>  tests/test-util-filemonitor.c | 685 ++++++++++++++++++++++++++++++++++
>  util/Makefile.objs            |   3 +
>  util/filemonitor-inotify.c    | 338 +++++++++++++++++
>  util/filemonitor-stub.c       |  59 +++
>  util/trace-events             |   9 +
>  9 files changed, 1233 insertions(+), 1 deletion(-)
>  create mode 100644 include/qemu/filemonitor.h
>  create mode 100644 tests/test-util-filemonitor.c
>  create mode 100644 util/filemonitor-inotify.c
>  create mode 100644 util/filemonitor-stub.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index ffb029f63a..5989796fa9 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -2080,6 +2080,13 @@ F: include/qemu/sockets.h
>  F: util/qemu-sockets.c
>  F: qapi/sockets.json
>
> +File monitor
> +M: Daniel P. Berrange <berrange@redhat.com>
> +S: Odd fixes
> +F: util/filemonitor*.c
> +F: include/qemu/filemonitor.h
> +F: tests/test-util-filemonitor.c
> +
>  Throttling infrastructure
>  M: Alberto Garcia <berto@igalia.com>
>  S: Supported
> diff --git a/Makefile.objs b/Makefile.objs
> index b7aae33367..fee0ce9fc5 100644
> --- a/Makefile.objs
> +++ b/Makefile.objs
> @@ -4,7 +4,7 @@ QAPI_MODULES += ui
>
>  #######################################################################
>  # Common libraries for tools and emulators
> -stub-obj-y = stubs/ crypto/
> +stub-obj-y = stubs/ util/ crypto/
>  util-obj-y = util/ qobject/ qapi/
>  util-obj-y += qapi/qapi-builtin-types.o
>  util-obj-y += qapi/qapi-types.o
> diff --git a/include/qemu/filemonitor.h b/include/qemu/filemonitor.h
> new file mode 100644
> index 0000000000..cd031832ed
> --- /dev/null
> +++ b/include/qemu/filemonitor.h
> @@ -0,0 +1,128 @@
> +/*
> + * QEMU file monitor helper
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#ifndef QEMU_FILE_MONITOR_H
> +#define QEMU_FILE_MONITOR_H
> +
> +#include "qemu-common.h"
> +
> +
> +typedef struct QFileMonitor QFileMonitor;
> +
> +typedef enum {
> +    /* File has been created in a dir */
> +    QFILE_MONITOR_EVENT_CREATED,
> +    /* File has been modified in a dir */
> +    QFILE_MONITOR_EVENT_MODIFIED,
> +    /* File has been deleted in a dir */
> +    QFILE_MONITOR_EVENT_DELETED,
> +    /* File has attributes changed */
> +    QFILE_MONITOR_EVENT_ATTRIBUTES,
> +    /* Dir is no longer being monitored (due to deletion) */
> +    QFILE_MONITOR_EVENT_IGNORED,
> +} QFileMonitorEvent;
> +
> +
> +/**
> + * QFileMonitorHandler:
> + * @id: id from qemu_file_monitor_add_watch()
> + * @event: the file change that occurred
> + * @filename: the name of the file affected
> + * @opaque: opaque data provided to qemu_file_monitor_add_watch()
> + *
> + * Invoked whenever a file changes. If @event is
> + * QFILE_MONITOR_EVENT_IGNORED, @filename will be
> + * empty.
> + *
> + */
> +typedef void (*QFileMonitorHandler)(int id,
> +                                    QFileMonitorEvent event,
> +                                    const char *filename,
> +                                    void *opaque);
> +
> +/**
> + * qemu_file_monitor_new:
> + * @errp: pointer to a NULL-initialized error object
> + *
> + * Create a handle for a file monitoring object.
> + *
> + * This object does locking internally to enable it to be
> + * safe to use from multiple threads
> + *
> + * If the platform does not support file monitoring, an
> + * error will be reported. Likewise if file monitoring
> + * is supported, but cannot be initialized
> + *
> + * Currently this is implemented on Linux platforms with
> + * the inotify subsystem.
> + *
> + * Returns: the new monitoring object, or NULL on error
> + */
> +QFileMonitor *qemu_file_monitor_new(Error **errp);
> +
> +/**
> + * qemu_file_monitor_free:
> + * @mon: the file monitor context
> + *
> + * Free resources associated with the file monitor,
> + * including any currently registered watches.
> + */
> +void qemu_file_monitor_free(QFileMonitor *mon);
> +
> +/**
> + * qemu_file_monitor_add_watch:
> + * @mon: the file monitor context
> + * @dirpath: the directory whose contents to watch
> + * @filename: optional filename to filter on
> + * @cb: the function to invoke when @dirpath has changes
> + * @opaque: data to pass to @cb
> + * @errp: pointer to a NULL-initialized error object
> + *
> + * Register to receive notifications of changes
> + * in the directory @dirpath. All files in the
> + * directory will be monitored. If the caller is
> + * only interested in one specific file, @filename
> + * can be used to filter events.
> + *
> + * Returns: a positive integer watch ID, or -1 on error
> + */
> +int qemu_file_monitor_add_watch(QFileMonitor *mon,
> +                                const char *dirpath,
> +                                const char *filename,
> +                                QFileMonitorHandler cb,
> +                                void *opaque,
> +                                Error **errp);
> +
> +/**
> + * qemu_file_monitor_remove_watch:
> + * @mon: the file monitor context
> + * @dirpath: the directory whose contents to unwatch
> + * @id: id of the watch to remove
> + *
> + * Removes the file monitoring watch @id, associated
> + * with the directory @dirpath. This must never be
> + * called from a QFileMonitorHandler callback, or a
> + * deadlock will result.
> + */
> +void qemu_file_monitor_remove_watch(QFileMonitor *mon,
> +                                    const char *dirpath,
> +                                    int id);
> +
> +#endif /* QEMU_FILE_MONITOR_H */
> diff --git a/tests/Makefile.include b/tests/Makefile.include
> index b39e989f72..3a6a8d4b17 100644
> --- a/tests/Makefile.include
> +++ b/tests/Makefile.include
> @@ -114,6 +114,7 @@ ifneq (,$(findstring qemu-ga,$(TOOLS)))
>  check-unit-$(land,$(CONFIG_LINUX),$(CONFIG_VIRTIO_SERIAL)) += tests/test-qga$(EXESUF)
>  endif
>  check-unit-y += tests/test-timed-average$(EXESUF)
> +check-unit-$(CONFIG_INOTIFY1) += tests/test-util-filemonitor$(EXESUF)
>  check-unit-y += tests/test-util-sockets$(EXESUF)
>  check-unit-y += tests/test-io-task$(EXESUF)
>  check-unit-y += tests/test-io-channel-socket$(EXESUF)
> @@ -657,6 +658,8 @@ tests/test-crypto-tlssession$(EXESUF): tests/test-crypto-tlssession.o \
>         tests/crypto-tls-x509-helpers.o tests/pkix_asn1_tab.o \
>         tests/crypto-tls-psk-helpers.o \
>          $(test-crypto-obj-y)
> +tests/test-util-filemonitor$(EXESUF): tests/test-util-filemonitor.o \
> +       $(test-util-obj-y)
>  tests/test-util-sockets$(EXESUF): tests/test-util-sockets.o \
>         tests/socket-helpers.o $(test-util-obj-y)
>  tests/test-io-task$(EXESUF): tests/test-io-task.o $(test-io-obj-y)
> diff --git a/tests/test-util-filemonitor.c b/tests/test-util-filemonitor.c
> new file mode 100644
> index 0000000000..5d95cea5ee
> --- /dev/null
> +++ b/tests/test-util-filemonitor.c
> @@ -0,0 +1,685 @@
> +/*
> + * Tests for util/filemonitor-*.c
> + *
> + * Copyright 2018 Red Hat, Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/main-loop.h"
> +#include "qapi/error.h"
> +#include "qemu/filemonitor.h"
> +
> +#include <utime.h>
> +
> +enum {
> +    QFILE_MONITOR_TEST_OP_CREATE,
> +    QFILE_MONITOR_TEST_OP_APPEND,
> +    QFILE_MONITOR_TEST_OP_TRUNC,
> +    QFILE_MONITOR_TEST_OP_RENAME,
> +    QFILE_MONITOR_TEST_OP_TOUCH,
> +    QFILE_MONITOR_TEST_OP_UNLINK,
> +};
> +
> +typedef struct {
> +    int type;
> +    const char *filesrc;
> +    const char *filedst;
> +} QFileMonitorTestOp;
> +
> +typedef struct {
> +    const char *file;
> +} QFileMonitorTestWatch;
> +
> +typedef struct {
> +    gsize nwatches;
> +    const QFileMonitorTestWatch *watches;
> +
> +    gsize nops;
> +    const QFileMonitorTestOp *ops;
> +} QFileMonitorTestPlan;
> +
> +typedef struct {
> +    int id;
> +    QFileMonitorEvent event;
> +    char *filename;
> +} QFileMonitorTestRecord;
> +
> +
> +typedef struct {
> +    QemuMutex lock;
> +    GList *records;
> +} QFileMonitorTestData;
> +
> +static QemuMutex evlock;
> +static bool evstopping;
> +static bool evrunning;
> +
> +/*
> + * Main function for a background thread that is
> + * running the event loop during the test
> + */
> +static void *
> +qemu_file_monitor_test_event_loop(void *opaque G_GNUC_UNUSED)
> +{
> +    qemu_mutex_lock(&evlock);
> +
> +    while (!evstopping) {
> +        qemu_mutex_unlock(&evlock);
> +        main_loop_wait(true);
> +        qemu_mutex_lock(&evlock);
> +    }
> +
> +    evrunning = false;
> +    qemu_mutex_unlock(&evlock);
> +    return NULL;
> +}
> +
> +
> +/*
> + * File monitor event handler which simply maintains
> + * an ordered list of all events that it receives
> + */
> +static void
> +qemu_file_monitor_test_handler(int id,
> +                               QFileMonitorEvent event,
> +                               const char *filename,
> +                               void *opaque)
> +{
> +    QFileMonitorTestData *data = opaque;
> +    QFileMonitorTestRecord *rec = g_new0(QFileMonitorTestRecord, 1);
> +
> +    rec->id = id;
> +    rec->event = event;
> +    rec->filename = g_strdup(filename);
> +
> +    qemu_mutex_lock(&data->lock);
> +    data->records = g_list_append(data->records, rec);
> +    qemu_mutex_unlock(&data->lock);
> +}
> +
> +
> +static void
> +qemu_file_monitor_test_record_free(QFileMonitorTestRecord *rec)
> +{
> +    g_free(rec->filename);
> +    g_free(rec);
> +}
> +
> +
> +/*
> + * Get the next event record that has been received by
> + * the file monitor event handler. Since events are
> + * emitted in the background thread running the event
> + * loop, we can't assume there is a record available
> + * immediately. Thus we will sleep for upto 5 seconds
> + * to wait for the event to be queued for us.
> + */
> +static QFileMonitorTestRecord *
> +qemu_file_monitor_test_next_record(QFileMonitorTestData *data)
> +{
> +    GTimer *timer = g_timer_new();
> +    QFileMonitorTestRecord *record = NULL;
> +    GList *tmp;
> +
> +    qemu_mutex_lock(&data->lock);
> +    while (!data->records && g_timer_elapsed(timer, NULL) < 5) {
> +        qemu_mutex_unlock(&data->lock);
> +        usleep(10 * 1000);
> +        qemu_mutex_lock(&data->lock);
> +    }
> +    if (data->records) {
> +        record = data->records->data;
> +        tmp = data->records;
> +        data->records = g_list_remove_link(data->records, tmp);
> +        g_list_free(tmp);
> +    }
> +    qemu_mutex_unlock(&data->lock);
> +
> +    g_timer_destroy(timer);
> +    return record;
> +}
> +
> +
> +/*
> + * Check whether the event record we retrieved matches
> + * data we were expecting to see for the event
> + */
> +static bool
> +qemu_file_monitor_test_expect(QFileMonitorTestData *data,
> +                              int id,
> +                              QFileMonitorEvent event,
> +                              const char *filename)
> +{
> +    QFileMonitorTestRecord *rec;
> +    bool ret = false;
> +
> +    rec = qemu_file_monitor_test_next_record(data);
> +
> +    if (!rec) {
> +        g_printerr("Missing event watch id %d event %d file %s\n",
> +                   id, event, filename);
> +        return false;
> +    }
> +
> +    if (id != rec->id) {
> +        g_printerr("Expected watch id %d but got %d\n", id, rec->id);
> +        goto cleanup;
> +    }
> +
> +    if (event != rec->event) {
> +        g_printerr("Expected event %d but got %d\n", event, rec->event);
> +        goto cleanup;
> +    }
> +
> +    if (!g_str_equal(filename, rec->filename)) {
> +        g_printerr("Expected filename %s but got %s\n",
> +                   filename, rec->filename);
> +        goto cleanup;
> +    }
> +
> +    ret = true;
> +
> + cleanup:
> +    qemu_file_monitor_test_record_free(rec);
> +    return ret;
> +}
> +
> +
> +static void
> +test_file_monitor_events(const void *opaque)
> +{
> +    const QFileMonitorTestPlan *plan = opaque;
> +    Error *local_err = NULL;
> +    GError *gerr = NULL;
> +    QFileMonitor *mon = qemu_file_monitor_new(&local_err);
> +    QemuThread th;
> +    GTimer *timer;
> +    gchar *dir = NULL;
> +    int err = -1;
> +    gsize i, j;
> +    char *pathsrc = NULL;
> +    char *pathdst = NULL;
> +    QFileMonitorTestData data;
> +
> +    qemu_mutex_init(&data.lock);
> +    data.records = NULL;
> +
> +    /*
> +     * The file monitor needs the main loop running in
> +     * order to receive events from inotify. We must
> +     * thus spawn a background thread to run an event
> +     * loop impl, while this thread triggers the
> +     * actual file operations we're testing
> +     */
> +    evrunning = 1;
> +    evstopping = 0;
> +    qemu_thread_create(&th, "event-loop",
> +                       qemu_file_monitor_test_event_loop, NULL,
> +                       QEMU_THREAD_JOINABLE);
> +
> +    if (local_err) {
> +        g_printerr("File monitoring not available: %s",
> +                   error_get_pretty(local_err));
> +        error_free(local_err);
> +        return;
> +    }
> +
> +    dir = g_dir_make_tmp("test-util-filemonitor-XXXXXX",
> +                         &gerr);
> +    if (!dir) {
> +        g_printerr("Unable to create tmp dir %s",
> +                   gerr->message);
> +        g_error_free(gerr);
> +        abort();
> +    }
> +
> +    /*
> +     * First register all the directory / file watches
> +     * we're interested in seeing events against
> +     */
> +    for (i = 0; i < plan->nwatches; i++) {
> +        int watchid;
> +        watchid = qemu_file_monitor_add_watch(mon,
> +                                              dir,
> +                                              plan->watches[i].file,
> +                                              qemu_file_monitor_test_handler,
> +                                              &data,
> +                                              &local_err);
> +        if (watchid < 0) {
> +            g_printerr("Unable to add watch %s",
> +                       error_get_pretty(local_err));
> +            goto cleanup;
> +        }
> +    }
> +
> +
> +    /*
> +     * Now invoke all the file operations (create,
> +     * delete, rename, chmod, etc). These operations
> +     * will trigger the various file monitor events
> +     */
> +    for (i = 0; i < plan->nops; i++) {
> +        const QFileMonitorTestOp *op = &(plan->ops[i]);
> +        int fd;
> +        struct utimbuf ubuf;
> +
> +        pathsrc = g_strdup_printf("%s/%s", dir, op->filesrc);
> +        if (op->filedst) {
> +            pathdst = g_strdup_printf("%s/%s", dir, op->filedst);
> +        }
> +
> +        switch (op->type) {
> +        case QFILE_MONITOR_TEST_OP_CREATE:
> +            fd = open(pathsrc, O_WRONLY | O_CREAT, 0700);
> +            if (fd < 0) {
> +                g_printerr("Unable to create %s: %s",
> +                           pathsrc, strerror(errno));
> +                goto cleanup;
> +            }
> +            close(fd);
> +            break;
> +
> +        case QFILE_MONITOR_TEST_OP_APPEND:
> +            fd = open(pathsrc, O_WRONLY | O_APPEND, 0700);
> +            if (fd < 0) {
> +                g_printerr("Unable to open %s: %s",
> +                           pathsrc, strerror(errno));
> +                goto cleanup;
> +            }
> +
> +            if (write(fd, "Hello World", 10) != 10) {
> +                g_printerr("Unable to write %s: %s",
> +                           pathsrc, strerror(errno));
> +                close(fd);
> +                goto cleanup;
> +            }
> +            close(fd);
> +            break;
> +
> +        case QFILE_MONITOR_TEST_OP_TRUNC:
> +            if (truncate(pathsrc, 4) < 0) {
> +                g_printerr("Unable to truncate %s: %s",
> +                           pathsrc, strerror(errno));
> +                goto cleanup;
> +            }
> +            break;
> +
> +        case QFILE_MONITOR_TEST_OP_RENAME:
> +            if (rename(pathsrc, pathdst) < 0) {
> +                g_printerr("Unable to rename %s to %s: %s",
> +                           pathsrc, pathdst, strerror(errno));
> +                goto cleanup;
> +            }
> +            break;
> +
> +        case QFILE_MONITOR_TEST_OP_UNLINK:
> +            if (unlink(pathsrc) < 0) {
> +                g_printerr("Unable to unlink %s: %s",
> +                           pathsrc, strerror(errno));
> +                goto cleanup;
> +            }
> +            break;
> +
> +        case QFILE_MONITOR_TEST_OP_TOUCH:
> +            ubuf.actime = 1024;
> +            ubuf.modtime = 1025;
> +            if (utime(pathsrc, &ubuf) < 0) {
> +                g_printerr("Unable to touch %s: %s",
> +                           pathsrc, strerror(errno));
> +                goto cleanup;
> +            }
> +            break;
> +
> +        default:
> +            g_assert_not_reached();
> +        }
> +
> +        g_free(pathsrc);
> +        g_free(pathdst);
> +        pathsrc = pathdst = NULL;
> +    }
> +
> +
> +    /*
> +     * Finally validate that we have received all the events
> +     * we expect to see for the combination of watches and
> +     * file operations
> +     */
> +    for (i = 0; i < plan->nops; i++) {
> +        const QFileMonitorTestOp *op = &(plan->ops[i]);
> +
> +        switch (op->type) {
> +        case QFILE_MONITOR_TEST_OP_CREATE:
> +            for (j = 0; j < plan->nwatches; j++) {
> +                if (plan->watches[j].file &&
> +                    !g_str_equal(plan->watches[j].file, op->filesrc))
> +                    continue;
> +
> +                if (!qemu_file_monitor_test_expect(
> +                        &data, j, QFILE_MONITOR_EVENT_CREATED, op->filesrc))
> +                    goto cleanup;
> +            }
> +            break;
> +
> +        case QFILE_MONITOR_TEST_OP_APPEND:
> +        case QFILE_MONITOR_TEST_OP_TRUNC:
> +            for (j = 0; j < plan->nwatches; j++) {
> +                if (plan->watches[j].file &&
> +                    !g_str_equal(plan->watches[j].file, op->filesrc))
> +                    continue;
> +
> +                if (!qemu_file_monitor_test_expect(
> +                        &data, j, QFILE_MONITOR_EVENT_MODIFIED, op->filesrc))
> +                    goto cleanup;
> +            }
> +            break;
> +
> +        case QFILE_MONITOR_TEST_OP_RENAME:
> +            for (j = 0; j < plan->nwatches; j++) {
> +                if (plan->watches[j].file &&
> +                    !g_str_equal(plan->watches[j].file, op->filesrc))
> +                    continue;
> +
> +                if (!qemu_file_monitor_test_expect(
> +                        &data, j, QFILE_MONITOR_EVENT_DELETED, op->filesrc))
> +                    goto cleanup;
> +            }
> +
> +            for (j = 0; j < plan->nwatches; j++) {
> +                if (plan->watches[j].file &&
> +                    !g_str_equal(plan->watches[j].file, op->filedst))
> +                    continue;
> +
> +                if (!qemu_file_monitor_test_expect(
> +                        &data, j, QFILE_MONITOR_EVENT_CREATED, op->filedst))
> +                    goto cleanup;
> +            }
> +            break;
> +
> +        case QFILE_MONITOR_TEST_OP_TOUCH:
> +            for (j = 0; j < plan->nwatches; j++) {
> +                if (plan->watches[j].file &&
> +                    !g_str_equal(plan->watches[j].file, op->filesrc))
> +                    continue;
> +
> +                if (!qemu_file_monitor_test_expect(
> +                        &data, j, QFILE_MONITOR_EVENT_ATTRIBUTES, op->filesrc))
> +                    goto cleanup;
> +            }
> +            break;
> +
> +        case QFILE_MONITOR_TEST_OP_UNLINK:
> +            for (j = 0; j < plan->nwatches; j++) {
> +                if (plan->watches[j].file &&
> +                    !g_str_equal(plan->watches[j].file, op->filesrc))
> +                    continue;
> +
> +                if (!qemu_file_monitor_test_expect(
> +                        &data, j, QFILE_MONITOR_EVENT_DELETED, op->filesrc))
> +                    goto cleanup;
> +            }
> +            break;
> +
> +        default:
> +            g_assert_not_reached();
> +        }
> +    }
> +
> +    err = 0;
> +
> + cleanup:
> +    g_free(pathsrc);
> +    g_free(pathdst);
> +
> +    qemu_mutex_lock(&evlock);
> +    evstopping = 1;
> +    timer = g_timer_new();
> +    while (evrunning && g_timer_elapsed(timer, NULL) < 5) {
> +        qemu_mutex_unlock(&evlock);
> +        usleep(10 * 1000);
> +        qemu_mutex_lock(&evlock);
> +    }
> +    qemu_mutex_unlock(&evlock);
> +
> +    if (g_timer_elapsed(timer, NULL) >= 5) {
> +        g_printerr("Event loop failed to quit after 5 seconds\n");
> +    }
> +    g_timer_destroy(timer);
> +
> +    for (i = 0; i < plan->nops; i++) {
> +        const QFileMonitorTestOp *op = &(plan->ops[i]);
> +        pathsrc = g_strdup_printf("%s/%s", dir, op->filesrc);
> +        unlink(pathsrc);
> +        g_free(pathsrc);
> +        if (op->filedst) {
> +            pathdst = g_strdup_printf("%s/%s", dir, op->filedst);
> +            unlink(pathdst);
> +            g_free(pathdst);
> +        }
> +    }
> +
> +    qemu_file_monitor_free(mon);
> +    g_list_foreach(data.records,
> +                   (GFunc)qemu_file_monitor_test_record_free, NULL);
> +    g_list_free(data.records);
> +    qemu_mutex_destroy(&data.lock);
> +    if (dir) {
> +        rmdir(dir);
> +    }
> +    g_free(dir);
> +    g_assert(err == 0);
> +}
> +
> +
> +/*
> + * Set of structs which define which file name patterns
> + * we're trying to watch against. NULL, means all files
> + * in the directory
> + */
> +static const QFileMonitorTestWatch watches_any[] = {
> +    { NULL },
> +};
> +
> +static const QFileMonitorTestWatch watches_one[] = {
> +    { "one.txt" },
> +};
> +
> +static const QFileMonitorTestWatch watches_two[] = {
> +    { "two.txt" },
> +};
> +
> +static const QFileMonitorTestWatch watches_many[] = {
> +    { NULL },
> +    { "one.txt" },
> +    { "two.txt" },
> +};
> +
> +
> +/*
> + * Various sets of file operations we're going to
> + * trigger and validate events for
> + */
> +static const QFileMonitorTestOp ops_create_one[] = {
> +    { .type = QFILE_MONITOR_TEST_OP_CREATE,
> +      .filesrc = "one.txt", }
> +};
> +
> +static const QFileMonitorTestOp ops_delete_one[] = {
> +    { .type = QFILE_MONITOR_TEST_OP_CREATE,
> +      .filesrc = "one.txt", },
> +    { .type = QFILE_MONITOR_TEST_OP_UNLINK,
> +      .filesrc = "one.txt", }
> +};
> +
> +static const QFileMonitorTestOp ops_create_many[] = {
> +    { .type = QFILE_MONITOR_TEST_OP_CREATE,
> +      .filesrc = "one.txt", },
> +    { .type = QFILE_MONITOR_TEST_OP_CREATE,
> +      .filesrc = "two.txt", },
> +    { .type = QFILE_MONITOR_TEST_OP_CREATE,
> +      .filesrc = "three.txt", }
> +};
> +
> +static const QFileMonitorTestOp ops_rename_one[] = {
> +    { .type = QFILE_MONITOR_TEST_OP_CREATE,
> +      .filesrc = "one.txt", },
> +    { .type = QFILE_MONITOR_TEST_OP_RENAME,
> +      .filesrc = "one.txt", .filedst = "two.txt" }
> +};
> +
> +static const QFileMonitorTestOp ops_rename_many[] = {
> +    { .type = QFILE_MONITOR_TEST_OP_CREATE,
> +      .filesrc = "one.txt", },
> +    { .type = QFILE_MONITOR_TEST_OP_CREATE,
> +      .filesrc = "two.txt", },
> +    { .type = QFILE_MONITOR_TEST_OP_RENAME,
> +      .filesrc = "one.txt", .filedst = "two.txt" }
> +};
> +
> +static const QFileMonitorTestOp ops_append_one[] = {
> +    { .type = QFILE_MONITOR_TEST_OP_CREATE,
> +      .filesrc = "one.txt", },
> +    { .type = QFILE_MONITOR_TEST_OP_APPEND,
> +      .filesrc = "one.txt", },
> +};
> +
> +static const QFileMonitorTestOp ops_trunc_one[] = {
> +    { .type = QFILE_MONITOR_TEST_OP_CREATE,
> +      .filesrc = "one.txt", },
> +    { .type = QFILE_MONITOR_TEST_OP_TRUNC,
> +      .filesrc = "one.txt", },
> +};
> +
> +static const QFileMonitorTestOp ops_touch_one[] = {
> +    { .type = QFILE_MONITOR_TEST_OP_CREATE,
> +      .filesrc = "one.txt", },
> +    { .type = QFILE_MONITOR_TEST_OP_TOUCH,
> +      .filesrc = "one.txt", },
> +};
> +
> +
> +/*
> + * No we define data sets for the combinatorial
> + * expansion of file watches and operation sets
> + */
> +#define PLAN_DATA(o, w) \
> +    static const QFileMonitorTestPlan plan_ ## o ## _ ## w = { \
> +        .nops = G_N_ELEMENTS(ops_ ##o), \
> +        .ops = ops_ ##o, \
> +        .nwatches = G_N_ELEMENTS(watches_ ##w), \
> +        .watches = watches_ ## w, \
> +    }
> +
> +PLAN_DATA(create_one, any);
> +PLAN_DATA(create_one, one);
> +PLAN_DATA(create_one, two);
> +PLAN_DATA(create_one, many);
> +
> +PLAN_DATA(delete_one, any);
> +PLAN_DATA(delete_one, one);
> +PLAN_DATA(delete_one, two);
> +PLAN_DATA(delete_one, many);
> +
> +PLAN_DATA(create_many, any);
> +PLAN_DATA(create_many, one);
> +PLAN_DATA(create_many, two);
> +PLAN_DATA(create_many, many);
> +
> +PLAN_DATA(rename_one, any);
> +PLAN_DATA(rename_one, one);
> +PLAN_DATA(rename_one, two);
> +PLAN_DATA(rename_one, many);
> +
> +PLAN_DATA(rename_many, any);
> +PLAN_DATA(rename_many, one);
> +PLAN_DATA(rename_many, two);
> +PLAN_DATA(rename_many, many);
> +
> +PLAN_DATA(append_one, any);
> +PLAN_DATA(append_one, one);
> +PLAN_DATA(append_one, two);
> +PLAN_DATA(append_one, many);
> +
> +PLAN_DATA(trunc_one, any);
> +PLAN_DATA(trunc_one, one);
> +PLAN_DATA(trunc_one, two);
> +PLAN_DATA(trunc_one, many);
> +
> +PLAN_DATA(touch_one, any);
> +PLAN_DATA(touch_one, one);
> +PLAN_DATA(touch_one, two);
> +PLAN_DATA(touch_one, many);
> +
> +
> +int main(int argc, char **argv)
> +{
> +    g_test_init(&argc, &argv, NULL);
> +
> +    qemu_init_main_loop(&error_abort);
> +
> +    qemu_mutex_init(&evlock);
> +
> +    /*
> +     * Register test cases for the combinatorial
> +     * expansion of file watches and operation sets
> +     */
> +    #define PLAN_REGISTER(o, w)                                         \
> +        g_test_add_data_func("/util/filemonitor/" # o "/" # w,          \
> +                             &plan_ ## o ## _ ## w, test_file_monitor_events)
> +
> +    PLAN_REGISTER(create_one, any);
> +    PLAN_REGISTER(create_one, one);
> +    PLAN_REGISTER(create_one, two);
> +    PLAN_REGISTER(create_one, many);
> +
> +    PLAN_REGISTER(delete_one, any);
> +    PLAN_REGISTER(delete_one, one);
> +    PLAN_REGISTER(delete_one, two);
> +    PLAN_REGISTER(delete_one, many);
> +
> +    PLAN_REGISTER(create_many, any);
> +    PLAN_REGISTER(create_many, one);
> +    PLAN_REGISTER(create_many, two);
> +    PLAN_REGISTER(create_many, many);
> +
> +    PLAN_REGISTER(rename_one, any);
> +    PLAN_REGISTER(rename_one, one);
> +    PLAN_REGISTER(rename_one, two);
> +    PLAN_REGISTER(rename_one, many);
> +
> +    PLAN_REGISTER(rename_many, any);
> +    PLAN_REGISTER(rename_many, one);
> +    PLAN_REGISTER(rename_many, two);
> +    PLAN_REGISTER(rename_many, many);
> +
> +    PLAN_REGISTER(append_one, any);
> +    PLAN_REGISTER(append_one, one);
> +    PLAN_REGISTER(append_one, two);
> +    PLAN_REGISTER(append_one, many);
> +
> +    PLAN_REGISTER(trunc_one, any);
> +    PLAN_REGISTER(trunc_one, one);
> +    PLAN_REGISTER(trunc_one, two);
> +    PLAN_REGISTER(trunc_one, many);
> +
> +    PLAN_REGISTER(touch_one, any);
> +    PLAN_REGISTER(touch_one, one);
> +    PLAN_REGISTER(touch_one, two);
> +    PLAN_REGISTER(touch_one, many);
> +
> +    return g_test_run();
> +}
> diff --git a/util/Makefile.objs b/util/Makefile.objs
> index 0820923c18..c1dd746902 100644
> --- a/util/Makefile.objs
> +++ b/util/Makefile.objs
> @@ -50,5 +50,8 @@ util-obj-y += range.o
>  util-obj-y += stats64.o
>  util-obj-y += systemd.o
>  util-obj-y += iova-tree.o
> +util-obj-$(CONFIG_INOTIFY1) += filemonitor-inotify.o
>  util-obj-$(CONFIG_LINUX) += vfio-helpers.o
>  util-obj-$(CONFIG_OPENGL) += drm.o
> +
> +stub-obj-y += filemonitor-stub.o
> diff --git a/util/filemonitor-inotify.c b/util/filemonitor-inotify.c
> new file mode 100644
> index 0000000000..c5e0a6fee0
> --- /dev/null
> +++ b/util/filemonitor-inotify.c
> @@ -0,0 +1,338 @@
> +/*
> + * QEMU file monitor Linux inotify impl
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/filemonitor.h"
> +#include "qemu/main-loop.h"
> +#include "qemu/error-report.h"
> +#include "qapi/error.h"
> +#include "trace.h"
> +
> +#include <sys/inotify.h>
> +
> +struct QFileMonitor {
> +    int fd;
> +
> +    QemuMutex lock; /* protects dirs & idmap */
> +    GHashTable *dirs; /* dirname => QFileMonitorDir */
> +    GHashTable *idmap; /* inotify ID => dirname */
> +};
> +
> +
> +typedef struct {
> +    int id; /* watch ID */
> +    char *filename; /* optional filter */
> +    QFileMonitorHandler cb;
> +    void *opaque;
> +} QFileMonitorWatch;
> +
> +
> +typedef struct {
> +    char *path;
> +    int id; /* inotify ID */
> +    int nextid; /* watch ID counter */
> +    GArray *watches; /* QFileMonitorWatch elements */
> +} QFileMonitorDir;
> +
> +
> +static void qemu_file_monitor_watch(void *arg)
> +{
> +    QFileMonitor *mon = arg;
> +    char buf[4096]
> +        __attribute__ ((aligned(__alignof__(struct inotify_event))));
> +    int used = 0;
> +    int len;
> +
> +    qemu_mutex_lock(&mon->lock);
> +
> +    if (mon->fd == -1) {
> +        qemu_mutex_unlock(&mon->lock);
> +        return;
> +    }
> +
> +    len = read(mon->fd, buf, sizeof(buf));
> +
> +    if (len < 0) {
> +        if (errno != EAGAIN) {
> +            error_report("Failure monitoring inotify FD '%s',"
> +                         "disabling events", strerror(errno));
> +            goto cleanup;
> +        }
> +
> +        /* no more events right now */
> +        goto cleanup;
> +    }
> +
> +    /* Loop over all events in the buffer */
> +    while (used < len) {
> +        struct inotify_event *ev =
> +            (struct inotify_event *)(buf + used);
> +        const char *name = ev->len ? ev->name : "";
> +        QFileMonitorDir *dir = g_hash_table_lookup(mon->idmap,
> +                                                   GINT_TO_POINTER(ev->wd));
> +        uint32_t iev = ev->mask &
> +            (IN_CREATE | IN_MODIFY | IN_DELETE | IN_IGNORED |
> +             IN_MOVED_TO | IN_MOVED_FROM | IN_ATTRIB);
> +        int qev;
> +        gsize i;
> +
> +        used += sizeof(struct inotify_event) + ev->len;
> +
> +        if (!dir) {
> +            continue;
> +        }
> +
> +        /*
> +         * During a rename operation, the old name gets
> +         * IN_MOVED_FROM and the new name gets IN_MOVED_TO.
> +         * To simplify life for callers, we turn these into
> +         * DELETED and CREATED events
> +         */
> +        switch (iev) {
> +        case IN_CREATE:
> +        case IN_MOVED_TO:
> +            qev = QFILE_MONITOR_EVENT_CREATED;
> +            break;
> +        case IN_MODIFY:
> +            qev = QFILE_MONITOR_EVENT_MODIFIED;
> +            break;
> +        case IN_DELETE:
> +        case IN_MOVED_FROM:
> +            qev = QFILE_MONITOR_EVENT_DELETED;
> +            break;
> +        case IN_ATTRIB:
> +            qev = QFILE_MONITOR_EVENT_ATTRIBUTES;
> +            break;
> +        case IN_IGNORED:
> +            qev = QFILE_MONITOR_EVENT_IGNORED;
> +            break;
> +        default:
> +            g_assert_not_reached();
> +        }
> +
> +        trace_qemu_file_monitor_event(mon, dir->path, name, ev->mask, dir->id);
> +        for (i = 0; i < dir->watches->len; i++) {
> +            QFileMonitorWatch *watch = &g_array_index(dir->watches,
> +                                                      QFileMonitorWatch,
> +                                                      i);
> +
> +            if (watch->filename == NULL ||
> +                (name && g_str_equal(watch->filename, name))) {
> +                trace_qemu_file_monitor_dispatch(mon, dir->path, name,
> +                                                 qev, watch->cb,
> +                                                 watch->opaque, watch->id);
> +                watch->cb(watch->id, qev, name, watch->opaque);
> +            }
> +        }
> +    }
> +
> + cleanup:
> +    qemu_mutex_unlock(&mon->lock);
> +}
> +
> +
> +static void
> +qemu_file_monitor_dir_free(void *data)
> +{
> +    QFileMonitorDir *dir = data;
> +    gsize i;
> +
> +    for (i = 0; i < dir->watches->len; i++) {
> +        QFileMonitorWatch *watch = &g_array_index(dir->watches,
> +                                                  QFileMonitorWatch, i);
> +        g_free(watch->filename);
> +    }
> +    g_array_unref(dir->watches);
> +    g_free(dir->path);
> +    g_free(dir);
> +}
> +
> +
> +QFileMonitor *
> +qemu_file_monitor_new(Error **errp)
> +{
> +    int fd;
> +    QFileMonitor *mon;
> +
> +    fd = inotify_init1(IN_NONBLOCK);
> +    if (fd < 0) {
> +        error_setg_errno(errp, errno,
> +                         "Unable to initialize inotify");
> +        return NULL;
> +    }
> +
> +    mon = g_new0(QFileMonitor, 1);
> +    qemu_mutex_init(&mon->lock);
> +    mon->fd = fd;
> +
> +    mon->dirs = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
> +                                      qemu_file_monitor_dir_free);
> +    mon->idmap = g_hash_table_new(g_direct_hash, g_direct_equal);
> +
> +    trace_qemu_file_monitor_new(mon, mon->fd);
> +
> +    return mon;
> +}
> +
> +static gboolean
> +qemu_file_monitor_free_idle(void *opaque)
> +{
> +    QFileMonitor *mon = opaque;
> +
> +    if (!mon) {
> +        return G_SOURCE_REMOVE;
> +    }
> +
> +    qemu_mutex_lock(&mon->lock);
> +
> +    g_hash_table_unref(mon->idmap);
> +    g_hash_table_unref(mon->dirs);
> +
> +    qemu_mutex_unlock(&mon->lock);
> +
> +    qemu_mutex_destroy(&mon->lock);
> +    g_free(mon);
> +
> +    return G_SOURCE_REMOVE;
> +}
> +
> +void
> +qemu_file_monitor_free(QFileMonitor *mon)
> +{
> +    if (!mon) {
> +        return;
> +    }
> +
> +    qemu_mutex_lock(&mon->lock);
> +    if (mon->fd != -1) {
> +        qemu_set_fd_handler(mon->fd, NULL, NULL, NULL);
> +        close(mon->fd);
> +        mon->fd = -1;
> +    }
> +    qemu_mutex_unlock(&mon->lock);
> +
> +    /* Can't free it yet, because another thread
> +     * may be running event loop, so the inotify
> +     * callback might be pending. Using an idle
> +     * source ensures we'll only free after the
> +     * pending callback is done
> +     */
> +    g_idle_add((GSourceFunc)qemu_file_monitor_free_idle, mon);
> +}
> +
> +int
> +qemu_file_monitor_add_watch(QFileMonitor *mon,
> +                            const char *dirpath,
> +                            const char *filename,
> +                            QFileMonitorHandler cb,
> +                            void *opaque,
> +                            Error **errp)
> +{
> +    QFileMonitorDir *dir;
> +    QFileMonitorWatch watch;
> +    int ret = -1;
> +
> +    qemu_mutex_lock(&mon->lock);
> +    dir = g_hash_table_lookup(mon->dirs, dirpath);
> +    if (!dir) {
> +        int rv = inotify_add_watch(mon->fd, dirpath,
> +                                   IN_CREATE | IN_DELETE | IN_MODIFY |
> +                                   IN_MOVED_TO | IN_MOVED_FROM | IN_ATTRIB);
> +
> +        if (rv < 0) {
> +            error_setg_errno(errp, errno, "Unable to watch '%s'", dirpath);
> +            goto cleanup;
> +        }
> +
> +        trace_qemu_file_monitor_enable_watch(mon, dirpath, rv);
> +
> +        dir = g_new0(QFileMonitorDir, 1);
> +        dir->path = g_strdup(dirpath);
> +        dir->id = rv;
> +        dir->watches = g_array_new(FALSE, TRUE, sizeof(QFileMonitorWatch));
> +
> +        g_hash_table_insert(mon->dirs, dir->path, dir);
> +        g_hash_table_insert(mon->idmap, GINT_TO_POINTER(rv), dir);
> +
> +        if (g_hash_table_size(mon->dirs) == 1) {
> +            qemu_set_fd_handler(mon->fd, qemu_file_monitor_watch, NULL, mon);
> +        }
> +    }
> +
> +    watch.id = dir->nextid++;
> +    watch.filename = g_strdup(filename);
> +    watch.cb = cb;
> +    watch.opaque = opaque;
> +
> +    g_array_append_val(dir->watches, watch);
> +
> +    trace_qemu_file_monitor_add_watch(mon, dirpath,
> +                                      filename ? filename : "<none>",
> +                                      cb, opaque, watch.id);
> +
> +    ret = watch.id;
> +
> + cleanup:
> +    qemu_mutex_unlock(&mon->lock);
> +    return ret;
> +}
> +
> +
> +void qemu_file_monitor_remove_watch(QFileMonitor *mon,
> +                                    const char *dirpath,
> +                                    int id)
> +{
> +    QFileMonitorDir *dir;
> +    gsize i;
> +
> +    qemu_mutex_lock(&mon->lock);
> +
> +    trace_qemu_file_monitor_remove_watch(mon, dirpath, id);
> +
> +    dir = g_hash_table_lookup(mon->dirs, dirpath);
> +    if (!dir) {
> +        goto cleanup;
> +    }
> +
> +    for (i = 0; i < dir->watches->len; i++) {
> +        QFileMonitorWatch *watch = &g_array_index(dir->watches,
> +                                                  QFileMonitorWatch, i);
> +        if (watch->id == id) {
> +            g_free(watch->filename);
> +            g_array_remove_index(dir->watches, i);
> +            break;
> +        }
> +    }
> +
> +    if (dir->watches->len == 0) {
> +        inotify_rm_watch(mon->fd, dir->id);
> +        trace_qemu_file_monitor_disable_watch(mon, dir->path, dir->id);
> +
> +        g_hash_table_remove(mon->idmap, GINT_TO_POINTER(dir->id));
> +        g_hash_table_remove(mon->dirs, dir->path);
> +
> +        if (g_hash_table_size(mon->dirs) == 0) {
> +            qemu_set_fd_handler(mon->fd, NULL, NULL, NULL);
> +        }
> +    }
> +
> + cleanup:
> +    qemu_mutex_unlock(&mon->lock);
> +}
> diff --git a/util/filemonitor-stub.c b/util/filemonitor-stub.c
> new file mode 100644
> index 0000000000..48268b2bb6
> --- /dev/null
> +++ b/util/filemonitor-stub.c
> @@ -0,0 +1,59 @@
> +/*
> + * QEMU file monitor stub impl
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/filemonitor.h"
> +#include "qemu/error-report.h"
> +#include "qapi/error.h"
> +
> +
> +QFileMonitor *
> +qemu_file_monitor_new(Error **errp)
> +{
> +    error_setg(errp, "File monitoring not available on this platform");
> +    return NULL;
> +}
> +
> +
> +void
> +qemu_file_monitor_free(QFileMonitor *mon G_GNUC_UNUSED)
> +{
> +}
> +
> +
> +int
> +qemu_file_monitor_add_watch(QFileMonitor *mon G_GNUC_UNUSED,
> +                            const char *dirpath G_GNUC_UNUSED,
> +                            const char *filename G_GNUC_UNUSED,
> +                            QFileMonitorHandler cb G_GNUC_UNUSED,
> +                            void *opaque G_GNUC_UNUSED,
> +                            Error **errp)
> +{
> +    error_setg(errp, "File monitoring not available on this platform");
> +    return -1;
> +}
> +
> +
> +void
> +qemu_file_monitor_remove_watch(QFileMonitor *mon G_GNUC_UNUSED,
> +                               const char *dirpath G_GNUC_UNUSED,
> +                               int id G_GNUC_UNUSED)
> +{
> +}
> diff --git a/util/trace-events b/util/trace-events
> index 79569b7fdf..ff19b253e2 100644
> --- a/util/trace-events
> +++ b/util/trace-events
> @@ -21,6 +21,15 @@ buffer_move_empty(const char *buf, size_t len, const char *from) "%s: %zd bytes
>  buffer_move(const char *buf, size_t len, const char *from) "%s: %zd bytes from %s"
>  buffer_free(const char *buf, size_t len) "%s: capacity %zd"
>
> +# util/filemonitor.c
> +qemu_file_monitor_add_watch(void *mon, const char *dirpath, const char *filename, void *cb, void *opaque, int id) "File monitor %p add watch dir='%s' file='%s' cb=%p opaque=%p id=%u"
> +qemu_file_monitor_remove_watch(void *mon, const char *dirpath, int id) "File monitor %p remove watch dir='%s' id=%u"
> +qemu_file_monitor_new(void *mon, int fd) "File monitor %p created fd=%d"
> +qemu_file_monitor_enable_watch(void *mon, const char *dirpath, int id) "File monitor %p enable watch dir='%s' id=%u"
> +qemu_file_monitor_disable_watch(void *mon, const char *dirpath, int id) "Fle monitor %p disable watch dir='%s' id=%u"
> +qemu_file_monitor_event(void *mon, const char *dirpath, const char *filename, int mask, unsigned int id) "File monitor %p event dir='%s' file='%s' mask=0x%x id=%u"
> +qemu_file_monitor_dispatch(void *mon, const char *dirpath, const char *filename, int ev, void *cb, void *opaque, unsigned int id) "File monitor %p dispatch dir='%s' file='%s' ev=%d cb=%p opaque=%p id=%u"
> +
>  # util/qemu-coroutine.c
>  qemu_aio_coroutine_enter(void *ctx, void *from, void *to, void *opaque) "ctx %p from %p to %p opaque %p"
>  qemu_coroutine_yield(void *from, void *to) "from %p to %p"
> --
> 2.20.1
>
Bandan Das March 12, 2019, 11:07 p.m. UTC | #2
Daniel P. Berrangé <berrange@redhat.com> writes:
...
> +
> +int
> +qemu_file_monitor_add_watch(QFileMonitor *mon,
> +                            const char *dirpath,
> +                            const char *filename,
> +                            QFileMonitorHandler cb,
> +                            void *opaque,
> +                            Error **errp)
> +{
> +    QFileMonitorDir *dir;
> +    QFileMonitorWatch watch;
> +    int ret = -1;
> +
> +    qemu_mutex_lock(&mon->lock);
> +    dir = g_hash_table_lookup(mon->dirs, dirpath);
> +    if (!dir) {
> +        int rv = inotify_add_watch(mon->fd, dirpath,
> +                                   IN_CREATE | IN_DELETE | IN_MODIFY |
> +                                   IN_MOVED_TO | IN_MOVED_FROM | IN_ATTRIB);
> +
> +        if (rv < 0) {
> +            error_setg_errno(errp, errno, "Unable to watch '%s'", dirpath);
> +            goto cleanup;
> +        }
> +
> +        trace_qemu_file_monitor_enable_watch(mon, dirpath, rv);
> +
> +        dir = g_new0(QFileMonitorDir, 1);
> +        dir->path = g_strdup(dirpath);
> +        dir->id = rv;
> +        dir->watches = g_array_new(FALSE, TRUE, sizeof(QFileMonitorWatch));
> +
> +        g_hash_table_insert(mon->dirs, dir->path, dir);
> +        g_hash_table_insert(mon->idmap, GINT_TO_POINTER(rv), dir);
> +
> +        if (g_hash_table_size(mon->dirs) == 1) {
> +            qemu_set_fd_handler(mon->fd, qemu_file_monitor_watch, NULL, mon);
> +        }
> +    }
> +
> +    watch.id = dir->nextid++;
> +    watch.filename = g_strdup(filename);
> +    watch.cb = cb;
> +    watch.opaque = opaque;
> +
> +    g_array_append_val(dir->watches, watch);
> +
> +    trace_qemu_file_monitor_add_watch(mon, dirpath,
> +                                      filename ? filename : "<none>",
> +                                      cb, opaque, watch.id);
> +
> +    ret = watch.id;
> +

This seems to break usb-mtp since we are returning watch.id.
Why are we not returning dir->id here ? usb-mtp relies on the watch
descriptor to handle events.

Bandan

> + cleanup:
> +    qemu_mutex_unlock(&mon->lock);
> +    return ret;
> +}
> +
Daniel P. Berrangé March 13, 2019, 10:17 a.m. UTC | #3
On Tue, Mar 12, 2019 at 07:07:42PM -0400, Bandan Das wrote:
> Daniel P. Berrangé <berrange@redhat.com> writes:
> ...
> > +
> > +int
> > +qemu_file_monitor_add_watch(QFileMonitor *mon,
> > +                            const char *dirpath,
> > +                            const char *filename,
> > +                            QFileMonitorHandler cb,
> > +                            void *opaque,
> > +                            Error **errp)
> > +{
> > +    QFileMonitorDir *dir;
> > +    QFileMonitorWatch watch;
> > +    int ret = -1;
> > +
> > +    qemu_mutex_lock(&mon->lock);
> > +    dir = g_hash_table_lookup(mon->dirs, dirpath);
> > +    if (!dir) {
> > +        int rv = inotify_add_watch(mon->fd, dirpath,
> > +                                   IN_CREATE | IN_DELETE | IN_MODIFY |
> > +                                   IN_MOVED_TO | IN_MOVED_FROM | IN_ATTRIB);
> > +
> > +        if (rv < 0) {
> > +            error_setg_errno(errp, errno, "Unable to watch '%s'", dirpath);
> > +            goto cleanup;
> > +        }
> > +
> > +        trace_qemu_file_monitor_enable_watch(mon, dirpath, rv);
> > +
> > +        dir = g_new0(QFileMonitorDir, 1);
> > +        dir->path = g_strdup(dirpath);
> > +        dir->id = rv;
> > +        dir->watches = g_array_new(FALSE, TRUE, sizeof(QFileMonitorWatch));
> > +
> > +        g_hash_table_insert(mon->dirs, dir->path, dir);
> > +        g_hash_table_insert(mon->idmap, GINT_TO_POINTER(rv), dir);
> > +
> > +        if (g_hash_table_size(mon->dirs) == 1) {
> > +            qemu_set_fd_handler(mon->fd, qemu_file_monitor_watch, NULL, mon);
> > +        }
> > +    }
> > +
> > +    watch.id = dir->nextid++;
> > +    watch.filename = g_strdup(filename);
> > +    watch.cb = cb;
> > +    watch.opaque = opaque;
> > +
> > +    g_array_append_val(dir->watches, watch);
> > +
> > +    trace_qemu_file_monitor_add_watch(mon, dirpath,
> > +                                      filename ? filename : "<none>",
> > +                                      cb, opaque, watch.id);
> > +
> > +    ret = watch.id;
> > +
> 
> This seems to break usb-mtp since we are returning watch.id.
> Why are we not returning dir->id here ? usb-mtp relies on the watch
> descriptor to handle events.

dir->id is the low level inotify identifier. This is not supposed to be
visible to any calling code, since that should instead be using the
QEMU generated watch.id value.

Can you give more info on how usb-mtp broke ?  I tested USB MTP before
sending this series and I saw it correctly detecting create / delete
of files & reflecting this in the guest.


Regards,
Daniel
Bandan Das March 13, 2019, 5:19 p.m. UTC | #4
Daniel P. Berrangé <berrange@redhat.com> writes:

> On Tue, Mar 12, 2019 at 07:07:42PM -0400, Bandan Das wrote:
>> Daniel P. Berrangé <berrange@redhat.com> writes:
>> ...
>> > +
>> > +int
>> > +qemu_file_monitor_add_watch(QFileMonitor *mon,
>> > +                            const char *dirpath,
>> > +                            const char *filename,
>> > +                            QFileMonitorHandler cb,
>> > +                            void *opaque,
>> > +                            Error **errp)
>> > +{
>> > +    QFileMonitorDir *dir;
>> > +    QFileMonitorWatch watch;
>> > +    int ret = -1;
>> > +
>> > +    qemu_mutex_lock(&mon->lock);
>> > +    dir = g_hash_table_lookup(mon->dirs, dirpath);
>> > +    if (!dir) {
>> > +        int rv = inotify_add_watch(mon->fd, dirpath,
>> > +                                   IN_CREATE | IN_DELETE | IN_MODIFY |
>> > +                                   IN_MOVED_TO | IN_MOVED_FROM | IN_ATTRIB);
>> > +
>> > +        if (rv < 0) {
>> > +            error_setg_errno(errp, errno, "Unable to watch '%s'", dirpath);
>> > +            goto cleanup;
>> > +        }
>> > +
>> > +        trace_qemu_file_monitor_enable_watch(mon, dirpath, rv);
>> > +
>> > +        dir = g_new0(QFileMonitorDir, 1);
>> > +        dir->path = g_strdup(dirpath);
>> > +        dir->id = rv;
>> > +        dir->watches = g_array_new(FALSE, TRUE, sizeof(QFileMonitorWatch));
>> > +
>> > +        g_hash_table_insert(mon->dirs, dir->path, dir);
>> > +        g_hash_table_insert(mon->idmap, GINT_TO_POINTER(rv), dir);
>> > +
>> > +        if (g_hash_table_size(mon->dirs) == 1) {
>> > +            qemu_set_fd_handler(mon->fd, qemu_file_monitor_watch, NULL, mon);
>> > +        }
>> > +    }
>> > +
>> > +    watch.id = dir->nextid++;
>> > +    watch.filename = g_strdup(filename);
>> > +    watch.cb = cb;
>> > +    watch.opaque = opaque;
>> > +
>> > +    g_array_append_val(dir->watches, watch);
>> > +
>> > +    trace_qemu_file_monitor_add_watch(mon, dirpath,
>> > +                                      filename ? filename : "<none>",
>> > +                                      cb, opaque, watch.id);
>> > +
>> > +    ret = watch.id;
>> > +
>> 
>> This seems to break usb-mtp since we are returning watch.id.
>> Why are we not returning dir->id here ? usb-mtp relies on the watch
>> descriptor to handle events.
>
> dir->id is the low level inotify identifier. This is not supposed to be
> visible to any calling code, since that should instead be using the
> QEMU generated watch.id value.
>

I guessed so even though I had the urge to just post a patch and return
dir->id :)

> Can you give more info on how usb-mtp broke ?  I tested USB MTP before
> sending this series and I saw it correctly detecting create / delete
> of files & reflecting this in the guest.
>
This is what I think is happening:

Consider the mtproot to be /root/mtpshare and following directory hierarchy -
/root/mtpshare/dir1

Initiator:
User enters dir1
Host:
usb_mtp_object_readdir is called which calls qemu_file_monitor_add_watch()
qemu_file_monitor_add_watch returns 0. 

Initiator:
User attempts to create a directory "dir2" in dir1.
Host:
The callback file_monitor_event() is invoked, id is 0.
file_monitor_event calls:
                   MTPObject *parent = usb_mtp_object_lookup_id(s, 0);
which returns the object associated with /root/mtpshare.                   
Further below, usb_mtp_add_child() will try to add the newly
created object but it will fail because it's looking for it
in /root/mtpshare(watchid=0) not /root/mtpshare/dir1.

I believe the problem is that adding a watch point isn't returning
a unique identifier to identify the watch.

Bandan

>
> Regards,
> Daniel
Daniel P. Berrangé March 13, 2019, 6:16 p.m. UTC | #5
On Wed, Mar 13, 2019 at 01:19:58PM -0400, Bandan Das wrote:
> Daniel P. Berrangé <berrange@redhat.com> writes:
> 
> > On Tue, Mar 12, 2019 at 07:07:42PM -0400, Bandan Das wrote:
> >> Daniel P. Berrangé <berrange@redhat.com> writes:
> >> ...
> >> > +
> >> > +int
> >> > +qemu_file_monitor_add_watch(QFileMonitor *mon,
> >> > +                            const char *dirpath,
> >> > +                            const char *filename,
> >> > +                            QFileMonitorHandler cb,
> >> > +                            void *opaque,
> >> > +                            Error **errp)
> >> > +{
> >> > +    QFileMonitorDir *dir;
> >> > +    QFileMonitorWatch watch;
> >> > +    int ret = -1;
> >> > +
> >> > +    qemu_mutex_lock(&mon->lock);
> >> > +    dir = g_hash_table_lookup(mon->dirs, dirpath);
> >> > +    if (!dir) {
> >> > +        int rv = inotify_add_watch(mon->fd, dirpath,
> >> > +                                   IN_CREATE | IN_DELETE | IN_MODIFY |
> >> > +                                   IN_MOVED_TO | IN_MOVED_FROM | IN_ATTRIB);
> >> > +
> >> > +        if (rv < 0) {
> >> > +            error_setg_errno(errp, errno, "Unable to watch '%s'", dirpath);
> >> > +            goto cleanup;
> >> > +        }
> >> > +
> >> > +        trace_qemu_file_monitor_enable_watch(mon, dirpath, rv);
> >> > +
> >> > +        dir = g_new0(QFileMonitorDir, 1);
> >> > +        dir->path = g_strdup(dirpath);
> >> > +        dir->id = rv;
> >> > +        dir->watches = g_array_new(FALSE, TRUE, sizeof(QFileMonitorWatch));
> >> > +
> >> > +        g_hash_table_insert(mon->dirs, dir->path, dir);
> >> > +        g_hash_table_insert(mon->idmap, GINT_TO_POINTER(rv), dir);
> >> > +
> >> > +        if (g_hash_table_size(mon->dirs) == 1) {
> >> > +            qemu_set_fd_handler(mon->fd, qemu_file_monitor_watch, NULL, mon);
> >> > +        }
> >> > +    }
> >> > +
> >> > +    watch.id = dir->nextid++;
> >> > +    watch.filename = g_strdup(filename);
> >> > +    watch.cb = cb;
> >> > +    watch.opaque = opaque;
> >> > +
> >> > +    g_array_append_val(dir->watches, watch);
> >> > +
> >> > +    trace_qemu_file_monitor_add_watch(mon, dirpath,
> >> > +                                      filename ? filename : "<none>",
> >> > +                                      cb, opaque, watch.id);
> >> > +
> >> > +    ret = watch.id;
> >> > +
> >> 
> >> This seems to break usb-mtp since we are returning watch.id.
> >> Why are we not returning dir->id here ? usb-mtp relies on the watch
> >> descriptor to handle events.
> >
> > dir->id is the low level inotify identifier. This is not supposed to be
> > visible to any calling code, since that should instead be using the
> > QEMU generated watch.id value.
> >
> 
> I guessed so even though I had the urge to just post a patch and return
> dir->id :)
> 
> > Can you give more info on how usb-mtp broke ?  I tested USB MTP before
> > sending this series and I saw it correctly detecting create / delete
> > of files & reflecting this in the guest.
> >
> This is what I think is happening:
> 
> Consider the mtproot to be /root/mtpshare and following directory hierarchy -
> /root/mtpshare/dir1
> 
> Initiator:
> User enters dir1
> Host:
> usb_mtp_object_readdir is called which calls qemu_file_monitor_add_watch()
> qemu_file_monitor_add_watch returns 0. 
> 
> Initiator:
> User attempts to create a directory "dir2" in dir1.
> Host:
> The callback file_monitor_event() is invoked, id is 0.
> file_monitor_event calls:
>                    MTPObject *parent = usb_mtp_object_lookup_id(s, 0);
> which returns the object associated with /root/mtpshare.                   
> Further below, usb_mtp_add_child() will try to add the newly
> created object but it will fail because it's looking for it
> in /root/mtpshare(watchid=0) not /root/mtpshare/dir1.
> 
> I believe the problem is that adding a watch point isn't returning
> a unique identifier to identify the watch.

Yeah, I've found the problem and will send a fix tomorrow once I have
unit tests for it.

Regards,
Daniel
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index ffb029f63a..5989796fa9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2080,6 +2080,13 @@  F: include/qemu/sockets.h
 F: util/qemu-sockets.c
 F: qapi/sockets.json
 
+File monitor
+M: Daniel P. Berrange <berrange@redhat.com>
+S: Odd fixes
+F: util/filemonitor*.c
+F: include/qemu/filemonitor.h
+F: tests/test-util-filemonitor.c
+
 Throttling infrastructure
 M: Alberto Garcia <berto@igalia.com>
 S: Supported
diff --git a/Makefile.objs b/Makefile.objs
index b7aae33367..fee0ce9fc5 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -4,7 +4,7 @@  QAPI_MODULES += ui
 
 #######################################################################
 # Common libraries for tools and emulators
-stub-obj-y = stubs/ crypto/
+stub-obj-y = stubs/ util/ crypto/
 util-obj-y = util/ qobject/ qapi/
 util-obj-y += qapi/qapi-builtin-types.o
 util-obj-y += qapi/qapi-types.o
diff --git a/include/qemu/filemonitor.h b/include/qemu/filemonitor.h
new file mode 100644
index 0000000000..cd031832ed
--- /dev/null
+++ b/include/qemu/filemonitor.h
@@ -0,0 +1,128 @@ 
+/*
+ * QEMU file monitor helper
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QEMU_FILE_MONITOR_H
+#define QEMU_FILE_MONITOR_H
+
+#include "qemu-common.h"
+
+
+typedef struct QFileMonitor QFileMonitor;
+
+typedef enum {
+    /* File has been created in a dir */
+    QFILE_MONITOR_EVENT_CREATED,
+    /* File has been modified in a dir */
+    QFILE_MONITOR_EVENT_MODIFIED,
+    /* File has been deleted in a dir */
+    QFILE_MONITOR_EVENT_DELETED,
+    /* File has attributes changed */
+    QFILE_MONITOR_EVENT_ATTRIBUTES,
+    /* Dir is no longer being monitored (due to deletion) */
+    QFILE_MONITOR_EVENT_IGNORED,
+} QFileMonitorEvent;
+
+
+/**
+ * QFileMonitorHandler:
+ * @id: id from qemu_file_monitor_add_watch()
+ * @event: the file change that occurred
+ * @filename: the name of the file affected
+ * @opaque: opaque data provided to qemu_file_monitor_add_watch()
+ *
+ * Invoked whenever a file changes. If @event is
+ * QFILE_MONITOR_EVENT_IGNORED, @filename will be
+ * empty.
+ *
+ */
+typedef void (*QFileMonitorHandler)(int id,
+                                    QFileMonitorEvent event,
+                                    const char *filename,
+                                    void *opaque);
+
+/**
+ * qemu_file_monitor_new:
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Create a handle for a file monitoring object.
+ *
+ * This object does locking internally to enable it to be
+ * safe to use from multiple threads
+ *
+ * If the platform does not support file monitoring, an
+ * error will be reported. Likewise if file monitoring
+ * is supported, but cannot be initialized
+ *
+ * Currently this is implemented on Linux platforms with
+ * the inotify subsystem.
+ *
+ * Returns: the new monitoring object, or NULL on error
+ */
+QFileMonitor *qemu_file_monitor_new(Error **errp);
+
+/**
+ * qemu_file_monitor_free:
+ * @mon: the file monitor context
+ *
+ * Free resources associated with the file monitor,
+ * including any currently registered watches.
+ */
+void qemu_file_monitor_free(QFileMonitor *mon);
+
+/**
+ * qemu_file_monitor_add_watch:
+ * @mon: the file monitor context
+ * @dirpath: the directory whose contents to watch
+ * @filename: optional filename to filter on
+ * @cb: the function to invoke when @dirpath has changes
+ * @opaque: data to pass to @cb
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Register to receive notifications of changes
+ * in the directory @dirpath. All files in the
+ * directory will be monitored. If the caller is
+ * only interested in one specific file, @filename
+ * can be used to filter events.
+ *
+ * Returns: a positive integer watch ID, or -1 on error
+ */
+int qemu_file_monitor_add_watch(QFileMonitor *mon,
+                                const char *dirpath,
+                                const char *filename,
+                                QFileMonitorHandler cb,
+                                void *opaque,
+                                Error **errp);
+
+/**
+ * qemu_file_monitor_remove_watch:
+ * @mon: the file monitor context
+ * @dirpath: the directory whose contents to unwatch
+ * @id: id of the watch to remove
+ *
+ * Removes the file monitoring watch @id, associated
+ * with the directory @dirpath. This must never be
+ * called from a QFileMonitorHandler callback, or a
+ * deadlock will result.
+ */
+void qemu_file_monitor_remove_watch(QFileMonitor *mon,
+                                    const char *dirpath,
+                                    int id);
+
+#endif /* QEMU_FILE_MONITOR_H */
diff --git a/tests/Makefile.include b/tests/Makefile.include
index b39e989f72..3a6a8d4b17 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -114,6 +114,7 @@  ifneq (,$(findstring qemu-ga,$(TOOLS)))
 check-unit-$(land,$(CONFIG_LINUX),$(CONFIG_VIRTIO_SERIAL)) += tests/test-qga$(EXESUF)
 endif
 check-unit-y += tests/test-timed-average$(EXESUF)
+check-unit-$(CONFIG_INOTIFY1) += tests/test-util-filemonitor$(EXESUF)
 check-unit-y += tests/test-util-sockets$(EXESUF)
 check-unit-y += tests/test-io-task$(EXESUF)
 check-unit-y += tests/test-io-channel-socket$(EXESUF)
@@ -657,6 +658,8 @@  tests/test-crypto-tlssession$(EXESUF): tests/test-crypto-tlssession.o \
 	tests/crypto-tls-x509-helpers.o tests/pkix_asn1_tab.o \
 	tests/crypto-tls-psk-helpers.o \
         $(test-crypto-obj-y)
+tests/test-util-filemonitor$(EXESUF): tests/test-util-filemonitor.o \
+	$(test-util-obj-y)
 tests/test-util-sockets$(EXESUF): tests/test-util-sockets.o \
 	tests/socket-helpers.o $(test-util-obj-y)
 tests/test-io-task$(EXESUF): tests/test-io-task.o $(test-io-obj-y)
diff --git a/tests/test-util-filemonitor.c b/tests/test-util-filemonitor.c
new file mode 100644
index 0000000000..5d95cea5ee
--- /dev/null
+++ b/tests/test-util-filemonitor.c
@@ -0,0 +1,685 @@ 
+/*
+ * Tests for util/filemonitor-*.c
+ *
+ * Copyright 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/main-loop.h"
+#include "qapi/error.h"
+#include "qemu/filemonitor.h"
+
+#include <utime.h>
+
+enum {
+    QFILE_MONITOR_TEST_OP_CREATE,
+    QFILE_MONITOR_TEST_OP_APPEND,
+    QFILE_MONITOR_TEST_OP_TRUNC,
+    QFILE_MONITOR_TEST_OP_RENAME,
+    QFILE_MONITOR_TEST_OP_TOUCH,
+    QFILE_MONITOR_TEST_OP_UNLINK,
+};
+
+typedef struct {
+    int type;
+    const char *filesrc;
+    const char *filedst;
+} QFileMonitorTestOp;
+
+typedef struct {
+    const char *file;
+} QFileMonitorTestWatch;
+
+typedef struct {
+    gsize nwatches;
+    const QFileMonitorTestWatch *watches;
+
+    gsize nops;
+    const QFileMonitorTestOp *ops;
+} QFileMonitorTestPlan;
+
+typedef struct {
+    int id;
+    QFileMonitorEvent event;
+    char *filename;
+} QFileMonitorTestRecord;
+
+
+typedef struct {
+    QemuMutex lock;
+    GList *records;
+} QFileMonitorTestData;
+
+static QemuMutex evlock;
+static bool evstopping;
+static bool evrunning;
+
+/*
+ * Main function for a background thread that is
+ * running the event loop during the test
+ */
+static void *
+qemu_file_monitor_test_event_loop(void *opaque G_GNUC_UNUSED)
+{
+    qemu_mutex_lock(&evlock);
+
+    while (!evstopping) {
+        qemu_mutex_unlock(&evlock);
+        main_loop_wait(true);
+        qemu_mutex_lock(&evlock);
+    }
+
+    evrunning = false;
+    qemu_mutex_unlock(&evlock);
+    return NULL;
+}
+
+
+/*
+ * File monitor event handler which simply maintains
+ * an ordered list of all events that it receives
+ */
+static void
+qemu_file_monitor_test_handler(int id,
+                               QFileMonitorEvent event,
+                               const char *filename,
+                               void *opaque)
+{
+    QFileMonitorTestData *data = opaque;
+    QFileMonitorTestRecord *rec = g_new0(QFileMonitorTestRecord, 1);
+
+    rec->id = id;
+    rec->event = event;
+    rec->filename = g_strdup(filename);
+
+    qemu_mutex_lock(&data->lock);
+    data->records = g_list_append(data->records, rec);
+    qemu_mutex_unlock(&data->lock);
+}
+
+
+static void
+qemu_file_monitor_test_record_free(QFileMonitorTestRecord *rec)
+{
+    g_free(rec->filename);
+    g_free(rec);
+}
+
+
+/*
+ * Get the next event record that has been received by
+ * the file monitor event handler. Since events are
+ * emitted in the background thread running the event
+ * loop, we can't assume there is a record available
+ * immediately. Thus we will sleep for upto 5 seconds
+ * to wait for the event to be queued for us.
+ */
+static QFileMonitorTestRecord *
+qemu_file_monitor_test_next_record(QFileMonitorTestData *data)
+{
+    GTimer *timer = g_timer_new();
+    QFileMonitorTestRecord *record = NULL;
+    GList *tmp;
+
+    qemu_mutex_lock(&data->lock);
+    while (!data->records && g_timer_elapsed(timer, NULL) < 5) {
+        qemu_mutex_unlock(&data->lock);
+        usleep(10 * 1000);
+        qemu_mutex_lock(&data->lock);
+    }
+    if (data->records) {
+        record = data->records->data;
+        tmp = data->records;
+        data->records = g_list_remove_link(data->records, tmp);
+        g_list_free(tmp);
+    }
+    qemu_mutex_unlock(&data->lock);
+
+    g_timer_destroy(timer);
+    return record;
+}
+
+
+/*
+ * Check whether the event record we retrieved matches
+ * data we were expecting to see for the event
+ */
+static bool
+qemu_file_monitor_test_expect(QFileMonitorTestData *data,
+                              int id,
+                              QFileMonitorEvent event,
+                              const char *filename)
+{
+    QFileMonitorTestRecord *rec;
+    bool ret = false;
+
+    rec = qemu_file_monitor_test_next_record(data);
+
+    if (!rec) {
+        g_printerr("Missing event watch id %d event %d file %s\n",
+                   id, event, filename);
+        return false;
+    }
+
+    if (id != rec->id) {
+        g_printerr("Expected watch id %d but got %d\n", id, rec->id);
+        goto cleanup;
+    }
+
+    if (event != rec->event) {
+        g_printerr("Expected event %d but got %d\n", event, rec->event);
+        goto cleanup;
+    }
+
+    if (!g_str_equal(filename, rec->filename)) {
+        g_printerr("Expected filename %s but got %s\n",
+                   filename, rec->filename);
+        goto cleanup;
+    }
+
+    ret = true;
+
+ cleanup:
+    qemu_file_monitor_test_record_free(rec);
+    return ret;
+}
+
+
+static void
+test_file_monitor_events(const void *opaque)
+{
+    const QFileMonitorTestPlan *plan = opaque;
+    Error *local_err = NULL;
+    GError *gerr = NULL;
+    QFileMonitor *mon = qemu_file_monitor_new(&local_err);
+    QemuThread th;
+    GTimer *timer;
+    gchar *dir = NULL;
+    int err = -1;
+    gsize i, j;
+    char *pathsrc = NULL;
+    char *pathdst = NULL;
+    QFileMonitorTestData data;
+
+    qemu_mutex_init(&data.lock);
+    data.records = NULL;
+
+    /*
+     * The file monitor needs the main loop running in
+     * order to receive events from inotify. We must
+     * thus spawn a background thread to run an event
+     * loop impl, while this thread triggers the
+     * actual file operations we're testing
+     */
+    evrunning = 1;
+    evstopping = 0;
+    qemu_thread_create(&th, "event-loop",
+                       qemu_file_monitor_test_event_loop, NULL,
+                       QEMU_THREAD_JOINABLE);
+
+    if (local_err) {
+        g_printerr("File monitoring not available: %s",
+                   error_get_pretty(local_err));
+        error_free(local_err);
+        return;
+    }
+
+    dir = g_dir_make_tmp("test-util-filemonitor-XXXXXX",
+                         &gerr);
+    if (!dir) {
+        g_printerr("Unable to create tmp dir %s",
+                   gerr->message);
+        g_error_free(gerr);
+        abort();
+    }
+
+    /*
+     * First register all the directory / file watches
+     * we're interested in seeing events against
+     */
+    for (i = 0; i < plan->nwatches; i++) {
+        int watchid;
+        watchid = qemu_file_monitor_add_watch(mon,
+                                              dir,
+                                              plan->watches[i].file,
+                                              qemu_file_monitor_test_handler,
+                                              &data,
+                                              &local_err);
+        if (watchid < 0) {
+            g_printerr("Unable to add watch %s",
+                       error_get_pretty(local_err));
+            goto cleanup;
+        }
+    }
+
+
+    /*
+     * Now invoke all the file operations (create,
+     * delete, rename, chmod, etc). These operations
+     * will trigger the various file monitor events
+     */
+    for (i = 0; i < plan->nops; i++) {
+        const QFileMonitorTestOp *op = &(plan->ops[i]);
+        int fd;
+        struct utimbuf ubuf;
+
+        pathsrc = g_strdup_printf("%s/%s", dir, op->filesrc);
+        if (op->filedst) {
+            pathdst = g_strdup_printf("%s/%s", dir, op->filedst);
+        }
+
+        switch (op->type) {
+        case QFILE_MONITOR_TEST_OP_CREATE:
+            fd = open(pathsrc, O_WRONLY | O_CREAT, 0700);
+            if (fd < 0) {
+                g_printerr("Unable to create %s: %s",
+                           pathsrc, strerror(errno));
+                goto cleanup;
+            }
+            close(fd);
+            break;
+
+        case QFILE_MONITOR_TEST_OP_APPEND:
+            fd = open(pathsrc, O_WRONLY | O_APPEND, 0700);
+            if (fd < 0) {
+                g_printerr("Unable to open %s: %s",
+                           pathsrc, strerror(errno));
+                goto cleanup;
+            }
+
+            if (write(fd, "Hello World", 10) != 10) {
+                g_printerr("Unable to write %s: %s",
+                           pathsrc, strerror(errno));
+                close(fd);
+                goto cleanup;
+            }
+            close(fd);
+            break;
+
+        case QFILE_MONITOR_TEST_OP_TRUNC:
+            if (truncate(pathsrc, 4) < 0) {
+                g_printerr("Unable to truncate %s: %s",
+                           pathsrc, strerror(errno));
+                goto cleanup;
+            }
+            break;
+
+        case QFILE_MONITOR_TEST_OP_RENAME:
+            if (rename(pathsrc, pathdst) < 0) {
+                g_printerr("Unable to rename %s to %s: %s",
+                           pathsrc, pathdst, strerror(errno));
+                goto cleanup;
+            }
+            break;
+
+        case QFILE_MONITOR_TEST_OP_UNLINK:
+            if (unlink(pathsrc) < 0) {
+                g_printerr("Unable to unlink %s: %s",
+                           pathsrc, strerror(errno));
+                goto cleanup;
+            }
+            break;
+
+        case QFILE_MONITOR_TEST_OP_TOUCH:
+            ubuf.actime = 1024;
+            ubuf.modtime = 1025;
+            if (utime(pathsrc, &ubuf) < 0) {
+                g_printerr("Unable to touch %s: %s",
+                           pathsrc, strerror(errno));
+                goto cleanup;
+            }
+            break;
+
+        default:
+            g_assert_not_reached();
+        }
+
+        g_free(pathsrc);
+        g_free(pathdst);
+        pathsrc = pathdst = NULL;
+    }
+
+
+    /*
+     * Finally validate that we have received all the events
+     * we expect to see for the combination of watches and
+     * file operations
+     */
+    for (i = 0; i < plan->nops; i++) {
+        const QFileMonitorTestOp *op = &(plan->ops[i]);
+
+        switch (op->type) {
+        case QFILE_MONITOR_TEST_OP_CREATE:
+            for (j = 0; j < plan->nwatches; j++) {
+                if (plan->watches[j].file &&
+                    !g_str_equal(plan->watches[j].file, op->filesrc))
+                    continue;
+
+                if (!qemu_file_monitor_test_expect(
+                        &data, j, QFILE_MONITOR_EVENT_CREATED, op->filesrc))
+                    goto cleanup;
+            }
+            break;
+
+        case QFILE_MONITOR_TEST_OP_APPEND:
+        case QFILE_MONITOR_TEST_OP_TRUNC:
+            for (j = 0; j < plan->nwatches; j++) {
+                if (plan->watches[j].file &&
+                    !g_str_equal(plan->watches[j].file, op->filesrc))
+                    continue;
+
+                if (!qemu_file_monitor_test_expect(
+                        &data, j, QFILE_MONITOR_EVENT_MODIFIED, op->filesrc))
+                    goto cleanup;
+            }
+            break;
+
+        case QFILE_MONITOR_TEST_OP_RENAME:
+            for (j = 0; j < plan->nwatches; j++) {
+                if (plan->watches[j].file &&
+                    !g_str_equal(plan->watches[j].file, op->filesrc))
+                    continue;
+
+                if (!qemu_file_monitor_test_expect(
+                        &data, j, QFILE_MONITOR_EVENT_DELETED, op->filesrc))
+                    goto cleanup;
+            }
+
+            for (j = 0; j < plan->nwatches; j++) {
+                if (plan->watches[j].file &&
+                    !g_str_equal(plan->watches[j].file, op->filedst))
+                    continue;
+
+                if (!qemu_file_monitor_test_expect(
+                        &data, j, QFILE_MONITOR_EVENT_CREATED, op->filedst))
+                    goto cleanup;
+            }
+            break;
+
+        case QFILE_MONITOR_TEST_OP_TOUCH:
+            for (j = 0; j < plan->nwatches; j++) {
+                if (plan->watches[j].file &&
+                    !g_str_equal(plan->watches[j].file, op->filesrc))
+                    continue;
+
+                if (!qemu_file_monitor_test_expect(
+                        &data, j, QFILE_MONITOR_EVENT_ATTRIBUTES, op->filesrc))
+                    goto cleanup;
+            }
+            break;
+
+        case QFILE_MONITOR_TEST_OP_UNLINK:
+            for (j = 0; j < plan->nwatches; j++) {
+                if (plan->watches[j].file &&
+                    !g_str_equal(plan->watches[j].file, op->filesrc))
+                    continue;
+
+                if (!qemu_file_monitor_test_expect(
+                        &data, j, QFILE_MONITOR_EVENT_DELETED, op->filesrc))
+                    goto cleanup;
+            }
+            break;
+
+        default:
+            g_assert_not_reached();
+        }
+    }
+
+    err = 0;
+
+ cleanup:
+    g_free(pathsrc);
+    g_free(pathdst);
+
+    qemu_mutex_lock(&evlock);
+    evstopping = 1;
+    timer = g_timer_new();
+    while (evrunning && g_timer_elapsed(timer, NULL) < 5) {
+        qemu_mutex_unlock(&evlock);
+        usleep(10 * 1000);
+        qemu_mutex_lock(&evlock);
+    }
+    qemu_mutex_unlock(&evlock);
+
+    if (g_timer_elapsed(timer, NULL) >= 5) {
+        g_printerr("Event loop failed to quit after 5 seconds\n");
+    }
+    g_timer_destroy(timer);
+
+    for (i = 0; i < plan->nops; i++) {
+        const QFileMonitorTestOp *op = &(plan->ops[i]);
+        pathsrc = g_strdup_printf("%s/%s", dir, op->filesrc);
+        unlink(pathsrc);
+        g_free(pathsrc);
+        if (op->filedst) {
+            pathdst = g_strdup_printf("%s/%s", dir, op->filedst);
+            unlink(pathdst);
+            g_free(pathdst);
+        }
+    }
+
+    qemu_file_monitor_free(mon);
+    g_list_foreach(data.records,
+                   (GFunc)qemu_file_monitor_test_record_free, NULL);
+    g_list_free(data.records);
+    qemu_mutex_destroy(&data.lock);
+    if (dir) {
+        rmdir(dir);
+    }
+    g_free(dir);
+    g_assert(err == 0);
+}
+
+
+/*
+ * Set of structs which define which file name patterns
+ * we're trying to watch against. NULL, means all files
+ * in the directory
+ */
+static const QFileMonitorTestWatch watches_any[] = {
+    { NULL },
+};
+
+static const QFileMonitorTestWatch watches_one[] = {
+    { "one.txt" },
+};
+
+static const QFileMonitorTestWatch watches_two[] = {
+    { "two.txt" },
+};
+
+static const QFileMonitorTestWatch watches_many[] = {
+    { NULL },
+    { "one.txt" },
+    { "two.txt" },
+};
+
+
+/*
+ * Various sets of file operations we're going to
+ * trigger and validate events for
+ */
+static const QFileMonitorTestOp ops_create_one[] = {
+    { .type = QFILE_MONITOR_TEST_OP_CREATE,
+      .filesrc = "one.txt", }
+};
+
+static const QFileMonitorTestOp ops_delete_one[] = {
+    { .type = QFILE_MONITOR_TEST_OP_CREATE,
+      .filesrc = "one.txt", },
+    { .type = QFILE_MONITOR_TEST_OP_UNLINK,
+      .filesrc = "one.txt", }
+};
+
+static const QFileMonitorTestOp ops_create_many[] = {
+    { .type = QFILE_MONITOR_TEST_OP_CREATE,
+      .filesrc = "one.txt", },
+    { .type = QFILE_MONITOR_TEST_OP_CREATE,
+      .filesrc = "two.txt", },
+    { .type = QFILE_MONITOR_TEST_OP_CREATE,
+      .filesrc = "three.txt", }
+};
+
+static const QFileMonitorTestOp ops_rename_one[] = {
+    { .type = QFILE_MONITOR_TEST_OP_CREATE,
+      .filesrc = "one.txt", },
+    { .type = QFILE_MONITOR_TEST_OP_RENAME,
+      .filesrc = "one.txt", .filedst = "two.txt" }
+};
+
+static const QFileMonitorTestOp ops_rename_many[] = {
+    { .type = QFILE_MONITOR_TEST_OP_CREATE,
+      .filesrc = "one.txt", },
+    { .type = QFILE_MONITOR_TEST_OP_CREATE,
+      .filesrc = "two.txt", },
+    { .type = QFILE_MONITOR_TEST_OP_RENAME,
+      .filesrc = "one.txt", .filedst = "two.txt" }
+};
+
+static const QFileMonitorTestOp ops_append_one[] = {
+    { .type = QFILE_MONITOR_TEST_OP_CREATE,
+      .filesrc = "one.txt", },
+    { .type = QFILE_MONITOR_TEST_OP_APPEND,
+      .filesrc = "one.txt", },
+};
+
+static const QFileMonitorTestOp ops_trunc_one[] = {
+    { .type = QFILE_MONITOR_TEST_OP_CREATE,
+      .filesrc = "one.txt", },
+    { .type = QFILE_MONITOR_TEST_OP_TRUNC,
+      .filesrc = "one.txt", },
+};
+
+static const QFileMonitorTestOp ops_touch_one[] = {
+    { .type = QFILE_MONITOR_TEST_OP_CREATE,
+      .filesrc = "one.txt", },
+    { .type = QFILE_MONITOR_TEST_OP_TOUCH,
+      .filesrc = "one.txt", },
+};
+
+
+/*
+ * No we define data sets for the combinatorial
+ * expansion of file watches and operation sets
+ */
+#define PLAN_DATA(o, w) \
+    static const QFileMonitorTestPlan plan_ ## o ## _ ## w = { \
+        .nops = G_N_ELEMENTS(ops_ ##o), \
+        .ops = ops_ ##o, \
+        .nwatches = G_N_ELEMENTS(watches_ ##w), \
+        .watches = watches_ ## w, \
+    }
+
+PLAN_DATA(create_one, any);
+PLAN_DATA(create_one, one);
+PLAN_DATA(create_one, two);
+PLAN_DATA(create_one, many);
+
+PLAN_DATA(delete_one, any);
+PLAN_DATA(delete_one, one);
+PLAN_DATA(delete_one, two);
+PLAN_DATA(delete_one, many);
+
+PLAN_DATA(create_many, any);
+PLAN_DATA(create_many, one);
+PLAN_DATA(create_many, two);
+PLAN_DATA(create_many, many);
+
+PLAN_DATA(rename_one, any);
+PLAN_DATA(rename_one, one);
+PLAN_DATA(rename_one, two);
+PLAN_DATA(rename_one, many);
+
+PLAN_DATA(rename_many, any);
+PLAN_DATA(rename_many, one);
+PLAN_DATA(rename_many, two);
+PLAN_DATA(rename_many, many);
+
+PLAN_DATA(append_one, any);
+PLAN_DATA(append_one, one);
+PLAN_DATA(append_one, two);
+PLAN_DATA(append_one, many);
+
+PLAN_DATA(trunc_one, any);
+PLAN_DATA(trunc_one, one);
+PLAN_DATA(trunc_one, two);
+PLAN_DATA(trunc_one, many);
+
+PLAN_DATA(touch_one, any);
+PLAN_DATA(touch_one, one);
+PLAN_DATA(touch_one, two);
+PLAN_DATA(touch_one, many);
+
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    qemu_init_main_loop(&error_abort);
+
+    qemu_mutex_init(&evlock);
+
+    /*
+     * Register test cases for the combinatorial
+     * expansion of file watches and operation sets
+     */
+    #define PLAN_REGISTER(o, w)                                         \
+        g_test_add_data_func("/util/filemonitor/" # o "/" # w,          \
+                             &plan_ ## o ## _ ## w, test_file_monitor_events)
+
+    PLAN_REGISTER(create_one, any);
+    PLAN_REGISTER(create_one, one);
+    PLAN_REGISTER(create_one, two);
+    PLAN_REGISTER(create_one, many);
+
+    PLAN_REGISTER(delete_one, any);
+    PLAN_REGISTER(delete_one, one);
+    PLAN_REGISTER(delete_one, two);
+    PLAN_REGISTER(delete_one, many);
+
+    PLAN_REGISTER(create_many, any);
+    PLAN_REGISTER(create_many, one);
+    PLAN_REGISTER(create_many, two);
+    PLAN_REGISTER(create_many, many);
+
+    PLAN_REGISTER(rename_one, any);
+    PLAN_REGISTER(rename_one, one);
+    PLAN_REGISTER(rename_one, two);
+    PLAN_REGISTER(rename_one, many);
+
+    PLAN_REGISTER(rename_many, any);
+    PLAN_REGISTER(rename_many, one);
+    PLAN_REGISTER(rename_many, two);
+    PLAN_REGISTER(rename_many, many);
+
+    PLAN_REGISTER(append_one, any);
+    PLAN_REGISTER(append_one, one);
+    PLAN_REGISTER(append_one, two);
+    PLAN_REGISTER(append_one, many);
+
+    PLAN_REGISTER(trunc_one, any);
+    PLAN_REGISTER(trunc_one, one);
+    PLAN_REGISTER(trunc_one, two);
+    PLAN_REGISTER(trunc_one, many);
+
+    PLAN_REGISTER(touch_one, any);
+    PLAN_REGISTER(touch_one, one);
+    PLAN_REGISTER(touch_one, two);
+    PLAN_REGISTER(touch_one, many);
+
+    return g_test_run();
+}
diff --git a/util/Makefile.objs b/util/Makefile.objs
index 0820923c18..c1dd746902 100644
--- a/util/Makefile.objs
+++ b/util/Makefile.objs
@@ -50,5 +50,8 @@  util-obj-y += range.o
 util-obj-y += stats64.o
 util-obj-y += systemd.o
 util-obj-y += iova-tree.o
+util-obj-$(CONFIG_INOTIFY1) += filemonitor-inotify.o
 util-obj-$(CONFIG_LINUX) += vfio-helpers.o
 util-obj-$(CONFIG_OPENGL) += drm.o
+
+stub-obj-y += filemonitor-stub.o
diff --git a/util/filemonitor-inotify.c b/util/filemonitor-inotify.c
new file mode 100644
index 0000000000..c5e0a6fee0
--- /dev/null
+++ b/util/filemonitor-inotify.c
@@ -0,0 +1,338 @@ 
+/*
+ * QEMU file monitor Linux inotify impl
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/filemonitor.h"
+#include "qemu/main-loop.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+#include "trace.h"
+
+#include <sys/inotify.h>
+
+struct QFileMonitor {
+    int fd;
+
+    QemuMutex lock; /* protects dirs & idmap */
+    GHashTable *dirs; /* dirname => QFileMonitorDir */
+    GHashTable *idmap; /* inotify ID => dirname */
+};
+
+
+typedef struct {
+    int id; /* watch ID */
+    char *filename; /* optional filter */
+    QFileMonitorHandler cb;
+    void *opaque;
+} QFileMonitorWatch;
+
+
+typedef struct {
+    char *path;
+    int id; /* inotify ID */
+    int nextid; /* watch ID counter */
+    GArray *watches; /* QFileMonitorWatch elements */
+} QFileMonitorDir;
+
+
+static void qemu_file_monitor_watch(void *arg)
+{
+    QFileMonitor *mon = arg;
+    char buf[4096]
+        __attribute__ ((aligned(__alignof__(struct inotify_event))));
+    int used = 0;
+    int len;
+
+    qemu_mutex_lock(&mon->lock);
+
+    if (mon->fd == -1) {
+        qemu_mutex_unlock(&mon->lock);
+        return;
+    }
+
+    len = read(mon->fd, buf, sizeof(buf));
+
+    if (len < 0) {
+        if (errno != EAGAIN) {
+            error_report("Failure monitoring inotify FD '%s',"
+                         "disabling events", strerror(errno));
+            goto cleanup;
+        }
+
+        /* no more events right now */
+        goto cleanup;
+    }
+
+    /* Loop over all events in the buffer */
+    while (used < len) {
+        struct inotify_event *ev =
+            (struct inotify_event *)(buf + used);
+        const char *name = ev->len ? ev->name : "";
+        QFileMonitorDir *dir = g_hash_table_lookup(mon->idmap,
+                                                   GINT_TO_POINTER(ev->wd));
+        uint32_t iev = ev->mask &
+            (IN_CREATE | IN_MODIFY | IN_DELETE | IN_IGNORED |
+             IN_MOVED_TO | IN_MOVED_FROM | IN_ATTRIB);
+        int qev;
+        gsize i;
+
+        used += sizeof(struct inotify_event) + ev->len;
+
+        if (!dir) {
+            continue;
+        }
+
+        /*
+         * During a rename operation, the old name gets
+         * IN_MOVED_FROM and the new name gets IN_MOVED_TO.
+         * To simplify life for callers, we turn these into
+         * DELETED and CREATED events
+         */
+        switch (iev) {
+        case IN_CREATE:
+        case IN_MOVED_TO:
+            qev = QFILE_MONITOR_EVENT_CREATED;
+            break;
+        case IN_MODIFY:
+            qev = QFILE_MONITOR_EVENT_MODIFIED;
+            break;
+        case IN_DELETE:
+        case IN_MOVED_FROM:
+            qev = QFILE_MONITOR_EVENT_DELETED;
+            break;
+        case IN_ATTRIB:
+            qev = QFILE_MONITOR_EVENT_ATTRIBUTES;
+            break;
+        case IN_IGNORED:
+            qev = QFILE_MONITOR_EVENT_IGNORED;
+            break;
+        default:
+            g_assert_not_reached();
+        }
+
+        trace_qemu_file_monitor_event(mon, dir->path, name, ev->mask, dir->id);
+        for (i = 0; i < dir->watches->len; i++) {
+            QFileMonitorWatch *watch = &g_array_index(dir->watches,
+                                                      QFileMonitorWatch,
+                                                      i);
+
+            if (watch->filename == NULL ||
+                (name && g_str_equal(watch->filename, name))) {
+                trace_qemu_file_monitor_dispatch(mon, dir->path, name,
+                                                 qev, watch->cb,
+                                                 watch->opaque, watch->id);
+                watch->cb(watch->id, qev, name, watch->opaque);
+            }
+        }
+    }
+
+ cleanup:
+    qemu_mutex_unlock(&mon->lock);
+}
+
+
+static void
+qemu_file_monitor_dir_free(void *data)
+{
+    QFileMonitorDir *dir = data;
+    gsize i;
+
+    for (i = 0; i < dir->watches->len; i++) {
+        QFileMonitorWatch *watch = &g_array_index(dir->watches,
+                                                  QFileMonitorWatch, i);
+        g_free(watch->filename);
+    }
+    g_array_unref(dir->watches);
+    g_free(dir->path);
+    g_free(dir);
+}
+
+
+QFileMonitor *
+qemu_file_monitor_new(Error **errp)
+{
+    int fd;
+    QFileMonitor *mon;
+
+    fd = inotify_init1(IN_NONBLOCK);
+    if (fd < 0) {
+        error_setg_errno(errp, errno,
+                         "Unable to initialize inotify");
+        return NULL;
+    }
+
+    mon = g_new0(QFileMonitor, 1);
+    qemu_mutex_init(&mon->lock);
+    mon->fd = fd;
+
+    mon->dirs = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
+                                      qemu_file_monitor_dir_free);
+    mon->idmap = g_hash_table_new(g_direct_hash, g_direct_equal);
+
+    trace_qemu_file_monitor_new(mon, mon->fd);
+
+    return mon;
+}
+
+static gboolean
+qemu_file_monitor_free_idle(void *opaque)
+{
+    QFileMonitor *mon = opaque;
+
+    if (!mon) {
+        return G_SOURCE_REMOVE;
+    }
+
+    qemu_mutex_lock(&mon->lock);
+
+    g_hash_table_unref(mon->idmap);
+    g_hash_table_unref(mon->dirs);
+
+    qemu_mutex_unlock(&mon->lock);
+
+    qemu_mutex_destroy(&mon->lock);
+    g_free(mon);
+
+    return G_SOURCE_REMOVE;
+}
+
+void
+qemu_file_monitor_free(QFileMonitor *mon)
+{
+    if (!mon) {
+        return;
+    }
+
+    qemu_mutex_lock(&mon->lock);
+    if (mon->fd != -1) {
+        qemu_set_fd_handler(mon->fd, NULL, NULL, NULL);
+        close(mon->fd);
+        mon->fd = -1;
+    }
+    qemu_mutex_unlock(&mon->lock);
+
+    /* Can't free it yet, because another thread
+     * may be running event loop, so the inotify
+     * callback might be pending. Using an idle
+     * source ensures we'll only free after the
+     * pending callback is done
+     */
+    g_idle_add((GSourceFunc)qemu_file_monitor_free_idle, mon);
+}
+
+int
+qemu_file_monitor_add_watch(QFileMonitor *mon,
+                            const char *dirpath,
+                            const char *filename,
+                            QFileMonitorHandler cb,
+                            void *opaque,
+                            Error **errp)
+{
+    QFileMonitorDir *dir;
+    QFileMonitorWatch watch;
+    int ret = -1;
+
+    qemu_mutex_lock(&mon->lock);
+    dir = g_hash_table_lookup(mon->dirs, dirpath);
+    if (!dir) {
+        int rv = inotify_add_watch(mon->fd, dirpath,
+                                   IN_CREATE | IN_DELETE | IN_MODIFY |
+                                   IN_MOVED_TO | IN_MOVED_FROM | IN_ATTRIB);
+
+        if (rv < 0) {
+            error_setg_errno(errp, errno, "Unable to watch '%s'", dirpath);
+            goto cleanup;
+        }
+
+        trace_qemu_file_monitor_enable_watch(mon, dirpath, rv);
+
+        dir = g_new0(QFileMonitorDir, 1);
+        dir->path = g_strdup(dirpath);
+        dir->id = rv;
+        dir->watches = g_array_new(FALSE, TRUE, sizeof(QFileMonitorWatch));
+
+        g_hash_table_insert(mon->dirs, dir->path, dir);
+        g_hash_table_insert(mon->idmap, GINT_TO_POINTER(rv), dir);
+
+        if (g_hash_table_size(mon->dirs) == 1) {
+            qemu_set_fd_handler(mon->fd, qemu_file_monitor_watch, NULL, mon);
+        }
+    }
+
+    watch.id = dir->nextid++;
+    watch.filename = g_strdup(filename);
+    watch.cb = cb;
+    watch.opaque = opaque;
+
+    g_array_append_val(dir->watches, watch);
+
+    trace_qemu_file_monitor_add_watch(mon, dirpath,
+                                      filename ? filename : "<none>",
+                                      cb, opaque, watch.id);
+
+    ret = watch.id;
+
+ cleanup:
+    qemu_mutex_unlock(&mon->lock);
+    return ret;
+}
+
+
+void qemu_file_monitor_remove_watch(QFileMonitor *mon,
+                                    const char *dirpath,
+                                    int id)
+{
+    QFileMonitorDir *dir;
+    gsize i;
+
+    qemu_mutex_lock(&mon->lock);
+
+    trace_qemu_file_monitor_remove_watch(mon, dirpath, id);
+
+    dir = g_hash_table_lookup(mon->dirs, dirpath);
+    if (!dir) {
+        goto cleanup;
+    }
+
+    for (i = 0; i < dir->watches->len; i++) {
+        QFileMonitorWatch *watch = &g_array_index(dir->watches,
+                                                  QFileMonitorWatch, i);
+        if (watch->id == id) {
+            g_free(watch->filename);
+            g_array_remove_index(dir->watches, i);
+            break;
+        }
+    }
+
+    if (dir->watches->len == 0) {
+        inotify_rm_watch(mon->fd, dir->id);
+        trace_qemu_file_monitor_disable_watch(mon, dir->path, dir->id);
+
+        g_hash_table_remove(mon->idmap, GINT_TO_POINTER(dir->id));
+        g_hash_table_remove(mon->dirs, dir->path);
+
+        if (g_hash_table_size(mon->dirs) == 0) {
+            qemu_set_fd_handler(mon->fd, NULL, NULL, NULL);
+        }
+    }
+
+ cleanup:
+    qemu_mutex_unlock(&mon->lock);
+}
diff --git a/util/filemonitor-stub.c b/util/filemonitor-stub.c
new file mode 100644
index 0000000000..48268b2bb6
--- /dev/null
+++ b/util/filemonitor-stub.c
@@ -0,0 +1,59 @@ 
+/*
+ * QEMU file monitor stub impl
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/filemonitor.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+
+
+QFileMonitor *
+qemu_file_monitor_new(Error **errp)
+{
+    error_setg(errp, "File monitoring not available on this platform");
+    return NULL;
+}
+
+
+void
+qemu_file_monitor_free(QFileMonitor *mon G_GNUC_UNUSED)
+{
+}
+
+
+int
+qemu_file_monitor_add_watch(QFileMonitor *mon G_GNUC_UNUSED,
+                            const char *dirpath G_GNUC_UNUSED,
+                            const char *filename G_GNUC_UNUSED,
+                            QFileMonitorHandler cb G_GNUC_UNUSED,
+                            void *opaque G_GNUC_UNUSED,
+                            Error **errp)
+{
+    error_setg(errp, "File monitoring not available on this platform");
+    return -1;
+}
+
+
+void
+qemu_file_monitor_remove_watch(QFileMonitor *mon G_GNUC_UNUSED,
+                               const char *dirpath G_GNUC_UNUSED,
+                               int id G_GNUC_UNUSED)
+{
+}
diff --git a/util/trace-events b/util/trace-events
index 79569b7fdf..ff19b253e2 100644
--- a/util/trace-events
+++ b/util/trace-events
@@ -21,6 +21,15 @@  buffer_move_empty(const char *buf, size_t len, const char *from) "%s: %zd bytes
 buffer_move(const char *buf, size_t len, const char *from) "%s: %zd bytes from %s"
 buffer_free(const char *buf, size_t len) "%s: capacity %zd"
 
+# util/filemonitor.c
+qemu_file_monitor_add_watch(void *mon, const char *dirpath, const char *filename, void *cb, void *opaque, int id) "File monitor %p add watch dir='%s' file='%s' cb=%p opaque=%p id=%u"
+qemu_file_monitor_remove_watch(void *mon, const char *dirpath, int id) "File monitor %p remove watch dir='%s' id=%u"
+qemu_file_monitor_new(void *mon, int fd) "File monitor %p created fd=%d"
+qemu_file_monitor_enable_watch(void *mon, const char *dirpath, int id) "File monitor %p enable watch dir='%s' id=%u"
+qemu_file_monitor_disable_watch(void *mon, const char *dirpath, int id) "Fle monitor %p disable watch dir='%s' id=%u"
+qemu_file_monitor_event(void *mon, const char *dirpath, const char *filename, int mask, unsigned int id) "File monitor %p event dir='%s' file='%s' mask=0x%x id=%u"
+qemu_file_monitor_dispatch(void *mon, const char *dirpath, const char *filename, int ev, void *cb, void *opaque, unsigned int id) "File monitor %p dispatch dir='%s' file='%s' ev=%d cb=%p opaque=%p id=%u"
+
 # util/qemu-coroutine.c
 qemu_aio_coroutine_enter(void *ctx, void *from, void *to, void *opaque) "ctx %p from %p to %p opaque %p"
 qemu_coroutine_yield(void *from, void *to) "from %p to %p"