Hi On Fri, Feb 15, 2019 at 6:13 PM Daniel P. Berrangé <berrange@xxxxxxxxxx> 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@xxxxxxxxxx> Reviewed-by: Marc-André Lureau <marcandre.lureau@xxxxxxxxxx> > --- > 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@xxxxxxxxxx> > +S: Odd fixes > +F: util/filemonitor*.c > +F: include/qemu/filemonitor.h > +F: tests/test-util-filemonitor.c > + > Throttling infrastructure > M: Alberto Garcia <berto@xxxxxxxxxx> > 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 >