util: add helper APIs for dealing with inotify in portable manner
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. Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
This commit is contained in:
parent
d88d85f1f0
commit
90e33dfec6
@ -2087,6 +2087,13 @@ F: include/qemu/sockets.h
|
|||||||
F: util/qemu-sockets.c
|
F: util/qemu-sockets.c
|
||||||
F: qapi/sockets.json
|
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
|
Throttling infrastructure
|
||||||
M: Alberto Garcia <berto@igalia.com>
|
M: Alberto Garcia <berto@igalia.com>
|
||||||
S: Supported
|
S: Supported
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#######################################################################
|
#######################################################################
|
||||||
# Common libraries for tools and emulators
|
# 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 = util/ qobject/ qapi/
|
||||||
|
|
||||||
chardev-obj-y = chardev/
|
chardev-obj-y = chardev/
|
||||||
|
128
include/qemu/filemonitor.h
Normal file
128
include/qemu/filemonitor.h
Normal file
@ -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 */
|
@ -114,6 +114,7 @@ ifneq (,$(findstring qemu-ga,$(TOOLS)))
|
|||||||
check-unit-$(land,$(CONFIG_LINUX),$(CONFIG_VIRTIO_SERIAL)) += tests/test-qga$(EXESUF)
|
check-unit-$(land,$(CONFIG_LINUX),$(CONFIG_VIRTIO_SERIAL)) += tests/test-qga$(EXESUF)
|
||||||
endif
|
endif
|
||||||
check-unit-y += tests/test-timed-average$(EXESUF)
|
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-util-sockets$(EXESUF)
|
||||||
check-unit-y += tests/test-io-task$(EXESUF)
|
check-unit-y += tests/test-io-task$(EXESUF)
|
||||||
check-unit-y += tests/test-io-channel-socket$(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-x509-helpers.o tests/pkix_asn1_tab.o \
|
||||||
tests/crypto-tls-psk-helpers.o \
|
tests/crypto-tls-psk-helpers.o \
|
||||||
$(test-crypto-obj-y)
|
$(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/test-util-sockets$(EXESUF): tests/test-util-sockets.o \
|
||||||
tests/socket-helpers.o $(test-util-obj-y)
|
tests/socket-helpers.o $(test-util-obj-y)
|
||||||
tests/test-io-task$(EXESUF): tests/test-io-task.o $(test-io-obj-y)
|
tests/test-io-task$(EXESUF): tests/test-io-task.o $(test-io-obj-y)
|
||||||
|
685
tests/test-util-filemonitor.c
Normal file
685
tests/test-util-filemonitor.c
Normal file
@ -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();
|
||||||
|
}
|
@ -50,5 +50,8 @@ util-obj-y += range.o
|
|||||||
util-obj-y += stats64.o
|
util-obj-y += stats64.o
|
||||||
util-obj-y += systemd.o
|
util-obj-y += systemd.o
|
||||||
util-obj-y += iova-tree.o
|
util-obj-y += iova-tree.o
|
||||||
|
util-obj-$(CONFIG_INOTIFY1) += filemonitor-inotify.o
|
||||||
util-obj-$(CONFIG_LINUX) += vfio-helpers.o
|
util-obj-$(CONFIG_LINUX) += vfio-helpers.o
|
||||||
util-obj-$(CONFIG_OPENGL) += drm.o
|
util-obj-$(CONFIG_OPENGL) += drm.o
|
||||||
|
|
||||||
|
stub-obj-y += filemonitor-stub.o
|
||||||
|
339
util/filemonitor-inotify.c
Normal file
339
util/filemonitor-inotify.c
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
59
util/filemonitor-stub.c
Normal file
59
util/filemonitor-stub.c
Normal file
@ -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)
|
||||||
|
{
|
||||||
|
}
|
@ -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_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"
|
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
|
# 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_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"
|
qemu_coroutine_yield(void *from, void *to) "from %p to %p"
|
||||||
|
Loading…
Reference in New Issue
Block a user