[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH v2 01/11] util: add helper APIs for dealing with ino
From: |
Daniel P . Berrangé |
Subject: |
[Qemu-devel] [PATCH v2 01/11] util: add helper APIs for dealing with inotify in portable manner |
Date: |
Fri, 15 Jun 2018 16:41:53 +0100 |
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é <address@hidden>
---
MAINTAINERS | 6 +
include/qemu/filemonitor.h | 117 ++++++++++++++
util/Makefile.objs | 1 +
util/filemonitor.c | 315 +++++++++++++++++++++++++++++++++++++
util/trace-events | 9 ++
5 files changed, 448 insertions(+)
create mode 100644 include/qemu/filemonitor.h
create mode 100644 util/filemonitor.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 8a94517e9e..4a4f7c514b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1771,6 +1771,12 @@ F: include/qemu/sockets.h
F: util/qemu-sockets.c
F: qapi/sockets.json
+File monitor
+M: Daniel P. Berrange <address@hidden>
+S: Odd fixes
+F: util/filemonitor.c
+F: include/qemu/filemonitor.h
+
Throttling infrastructure
M: Alberto Garcia <address@hidden>
S: Supported
diff --git a/include/qemu/filemonitor.h b/include/qemu/filemonitor.h
new file mode 100644
index 0000000000..1326272f0a
--- /dev/null
+++ b/include/qemu/filemonitor.h
@@ -0,0 +1,117 @@
+/*
+ * 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,
+ /* 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_get_instance:
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Acquire a handle to the shared 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 shared monitoring object, or NULL on error
+ */
+QFileMonitor *qemu_file_monitor_get_instance(Error **errp);
+
+/**
+ * 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/util/Makefile.objs b/util/Makefile.objs
index e1c3fed4dc..769d295a27 100644
--- a/util/Makefile.objs
+++ b/util/Makefile.objs
@@ -48,4 +48,5 @@ util-obj-y += range.o
util-obj-y += stats64.o
util-obj-y += systemd.o
util-obj-y += iova-tree.o
+util-obj-y += filemonitor.o
util-obj-$(CONFIG_LINUX) += vfio-helpers.o
diff --git a/util/filemonitor.c b/util/filemonitor.c
new file mode 100644
index 0000000000..67d7aedbe0
--- /dev/null
+++ b/util/filemonitor.c
@@ -0,0 +1,315 @@
+/*
+ * 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/>.
+ *
+ */
+
+#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"
+
+struct QFileMonitor {
+ QemuMutex lock;
+ int fd;
+
+ 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 */
+ gsize nwatches;
+ QFileMonitorWatch *watches;
+} QFileMonitorDir;
+
+
+#ifdef CONFIG_INOTIFY1
+#include <sys/inotify.h>
+
+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 = read(mon->fd, buf, sizeof(buf));
+
+ qemu_mutex_lock(&mon->lock);
+
+ if (len < 0) {
+ if (errno != EAGAIN) {
+ error_report("Failure monitoring inotify FD, disabling events");
+ 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);
+ 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_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->nwatches; i++) {
+ QFileMonitorWatch *watch = &dir->watches[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;
+
+ g_free(dir->watches);
+ g_free(dir);
+}
+
+#endif
+
+static QFileMonitor *
+qemu_file_monitor_new(Error **errp)
+{
+#ifdef CONFIG_INOTIFY1
+ 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;
+#else
+ error_setg(errp, "File monitoring not available on this platform");
+ return NULL;
+#endif
+}
+
+
+QFileMonitor *qemu_file_monitor_get_instance(Error **errp)
+{
+ static QFileMonitor *global;
+
+ if (!global) {
+ global = qemu_file_monitor_new(errp);
+ }
+
+ return global;
+}
+
+
+#ifdef CONFIG_INOTIFY1
+int
+qemu_file_monitor_add_watch(QFileMonitor *mon,
+ const char *dirpath,
+ const char *filename,
+ QFileMonitorHandler cb,
+ void *opaque,
+ Error **errp)
+{
+ QFileMonitorDir *dir;
+ 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);
+
+ 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;
+
+ 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);
+ }
+ }
+
+ dir->watches = g_renew(QFileMonitorWatch, dir->watches, dir->nwatches + 1);
+
+ dir->watches[dir->nwatches].id = ++dir->nextid;
+ dir->watches[dir->nwatches].filename = filename ? g_strdup(filename) :
NULL;
+ dir->watches[dir->nwatches].cb = cb;
+ dir->watches[dir->nwatches].opaque = opaque;
+ dir->nwatches++;
+
+ trace_qemu_file_monitor_add_watch(mon, dirpath,
+ filename ? filename : "<none>",
+ cb, opaque,
+ dir->watches[dir->nwatches - 1].id);
+
+ ret = 0;
+
+ 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->nwatches; i++) {
+ if (dir->watches[i].id == id) {
+ if (i < (dir->nwatches - 1)) {
+ memmove(dir->watches + i,
+ dir->watches + i + 1,
+ sizeof(QFileMonitorWatch) *
+ (dir->nwatches - (i + 1)));
+ dir->watches = g_renew(QFileMonitorWatch, dir->watches,
+ dir->nwatches - 1);
+ dir->nwatches--;
+ }
+ break;
+ }
+ }
+
+ if (dir->nwatches == 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);
+ }
+
+ cleanup:
+ qemu_mutex_lock(&mon->lock);
+}
+
+#else
+int
+qemu_file_monitor_add_watch(QFileMonitor *mon,
+ const char *dirpath,
+ const char *filename,
+ QFileMonitorHandler cb,
+ void *opaque,
+ Error **errp)
+{
+ error_setg(errp, "File monitoring not available on this platform");
+ return -1;
+}
+
+void qemu_file_monitor_remove_watch(QFileMonitor *mon,
+ const char *dirpath,
+ int id)
+{
+}
+#endif
+
diff --git a/util/trace-events b/util/trace-events
index 4822434c89..1a429f5525 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.17.0
- [Qemu-devel] [PATCH v2 00/11] Add a standard authorization framework, Daniel P . Berrangé, 2018/06/15
- [Qemu-devel] [PATCH v2 03/11] hw/usb: don't set IN_ISDIR for inotify watch in MTP driver, Daniel P . Berrangé, 2018/06/15
- [Qemu-devel] [PATCH v2 02/11] qom: don't require user creatable objects to be registered, Daniel P . Berrangé, 2018/06/15
- [Qemu-devel] [PATCH v2 04/11] hw/usb: fix const-ness for string params in MTP driver, Daniel P . Berrangé, 2018/06/15
- [Qemu-devel] [PATCH v2 01/11] util: add helper APIs for dealing with inotify in portable manner,
Daniel P . Berrangé <=
- [Qemu-devel] [PATCH v2 06/11] authz: add QAuthZ object as an authorization base class, Daniel P . Berrangé, 2018/06/15
- [Qemu-devel] [PATCH v2 05/11] hw/usb: switch MTP to use new inotify APIs, Daniel P . Berrangé, 2018/06/15
- [Qemu-devel] [PATCH v2 07/11] authz: add QAuthZSimple object type for easy whitelist auth checks, Daniel P . Berrangé, 2018/06/15
- [Qemu-devel] [PATCH v2 09/11] authz: add QAuthZListFile object type for a file access control list, Daniel P . Berrangé, 2018/06/15
- [Qemu-devel] [PATCH v2 08/11] authz: add QAuthZList object type for an access control list, Daniel P . Berrangé, 2018/06/15