| /* | 
 |  * 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 <glib/gstdio.h> | 
 |  | 
 | #include <utime.h> | 
 |  | 
 | enum { | 
 |     QFILE_MONITOR_TEST_OP_ADD_WATCH, | 
 |     QFILE_MONITOR_TEST_OP_DEL_WATCH, | 
 |     QFILE_MONITOR_TEST_OP_EVENT, | 
 |     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, | 
 |     QFILE_MONITOR_TEST_OP_MKDIR, | 
 |     QFILE_MONITOR_TEST_OP_RMDIR, | 
 | }; | 
 |  | 
 | typedef struct { | 
 |     int type; | 
 |     const char *filesrc; | 
 |     const char *filedst; | 
 |     int64_t *watchid; | 
 |     int eventid; | 
 |     /* | 
 |      * Only valid with OP_EVENT - this event might be | 
 |      * swapped with the next OP_EVENT | 
 |      */ | 
 |     bool swapnext; | 
 | } QFileMonitorTestOp; | 
 |  | 
 | typedef struct { | 
 |     int64_t id; | 
 |     QFileMonitorEvent event; | 
 |     char *filename; | 
 | } QFileMonitorTestRecord; | 
 |  | 
 |  | 
 | typedef struct { | 
 |     QemuMutex lock; | 
 |     GList *records; | 
 | } QFileMonitorTestData; | 
 |  | 
 | static QemuMutex evlock; | 
 | static bool evstopping; | 
 | static bool evrunning; | 
 | static bool debug; | 
 |  | 
 | /* | 
 |  * 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(int64_t id, | 
 |                                QFileMonitorEvent event, | 
 |                                const char *filename, | 
 |                                void *opaque) | 
 | { | 
 |     QFileMonitorTestData *data = opaque; | 
 |     QFileMonitorTestRecord *rec = g_new0(QFileMonitorTestRecord, 1); | 
 |  | 
 |     if (debug) { | 
 |         g_printerr("Queue event id %" PRIx64 " event %d file %s\n", | 
 |                    id, event, filename); | 
 |     } | 
 |     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 up to 5 seconds | 
 |  * to wait for the event to be queued for us. | 
 |  */ | 
 | static QFileMonitorTestRecord * | 
 | qemu_file_monitor_test_next_record(QFileMonitorTestData *data, | 
 |                                    QFileMonitorTestRecord *pushback) | 
 | { | 
 |     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; | 
 |         if (pushback) { | 
 |             data->records->data = pushback; | 
 |         } else { | 
 |             tmp = data->records; | 
 |             data->records = g_list_remove_link(data->records, tmp); | 
 |             g_list_free(tmp); | 
 |         } | 
 |     } else if (pushback) { | 
 |         qemu_file_monitor_test_record_free(pushback); | 
 |     } | 
 |     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, | 
 |                               int64_t id, | 
 |                               QFileMonitorEvent event, | 
 |                               const char *filename, | 
 |                               bool swapnext) | 
 | { | 
 |     QFileMonitorTestRecord *rec; | 
 |     bool ret = false; | 
 |  | 
 |     rec = qemu_file_monitor_test_next_record(data, NULL); | 
 |  | 
 |  retry: | 
 |     if (!rec) { | 
 |         g_printerr("Missing event watch id %" PRIx64 " event %d file %s\n", | 
 |                    id, event, filename); | 
 |         return false; | 
 |     } | 
 |  | 
 |     if (id != rec->id) { | 
 |         if (swapnext) { | 
 |             rec = qemu_file_monitor_test_next_record(data, rec); | 
 |             swapnext = false; | 
 |             goto retry; | 
 |         } | 
 |         g_printerr("Expected watch id %" PRIx64 " but got %" PRIx64 "\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(void) | 
 | { | 
 |     int64_t watch0 = 0; | 
 |     int64_t watch1 = 0; | 
 |     int64_t watch2 = 0; | 
 |     int64_t watch3 = 0; | 
 |     int64_t watch4 = 0; | 
 |     int64_t watch5 = 0; | 
 |     QFileMonitorTestOp ops[] = { | 
 |         { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH, | 
 |           .filesrc = NULL, .watchid = &watch0 }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH, | 
 |           .filesrc = "one.txt", .watchid = &watch1 }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH, | 
 |           .filesrc = "two.txt", .watchid = &watch2 }, | 
 |  | 
 |  | 
 |         { .type = QFILE_MONITOR_TEST_OP_CREATE, | 
 |           .filesrc = "one.txt", }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "one.txt", .watchid = &watch0, | 
 |           .eventid = QFILE_MONITOR_EVENT_CREATED }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "one.txt", .watchid = &watch1, | 
 |           .eventid = QFILE_MONITOR_EVENT_CREATED }, | 
 |  | 
 |  | 
 |         { .type = QFILE_MONITOR_TEST_OP_CREATE, | 
 |           .filesrc = "two.txt", }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "two.txt", .watchid = &watch0, | 
 |           .eventid = QFILE_MONITOR_EVENT_CREATED }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "two.txt", .watchid = &watch2, | 
 |           .eventid = QFILE_MONITOR_EVENT_CREATED }, | 
 |  | 
 |  | 
 |         { .type = QFILE_MONITOR_TEST_OP_CREATE, | 
 |           .filesrc = "three.txt", }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "three.txt", .watchid = &watch0, | 
 |           .eventid = QFILE_MONITOR_EVENT_CREATED }, | 
 |  | 
 |  | 
 |         { .type = QFILE_MONITOR_TEST_OP_UNLINK, | 
 |           .filesrc = "three.txt", }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "three.txt", .watchid = &watch0, | 
 |           .eventid = QFILE_MONITOR_EVENT_DELETED }, | 
 |  | 
 |  | 
 |         { .type = QFILE_MONITOR_TEST_OP_RENAME, | 
 |           .filesrc = "one.txt", .filedst = "two.txt" }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "one.txt", .watchid = &watch0, | 
 |           .eventid = QFILE_MONITOR_EVENT_DELETED }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "one.txt", .watchid = &watch1, | 
 |           .eventid = QFILE_MONITOR_EVENT_DELETED }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "two.txt", .watchid = &watch0, | 
 |           .eventid = QFILE_MONITOR_EVENT_CREATED }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "two.txt", .watchid = &watch2, | 
 |           .eventid = QFILE_MONITOR_EVENT_CREATED }, | 
 |  | 
 |  | 
 |         { .type = QFILE_MONITOR_TEST_OP_APPEND, | 
 |           .filesrc = "two.txt", }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "two.txt", .watchid = &watch0, | 
 |           .eventid = QFILE_MONITOR_EVENT_MODIFIED }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "two.txt", .watchid = &watch2, | 
 |           .eventid = QFILE_MONITOR_EVENT_MODIFIED }, | 
 |  | 
 |  | 
 |         { .type = QFILE_MONITOR_TEST_OP_TOUCH, | 
 |           .filesrc = "two.txt", }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "two.txt", .watchid = &watch0, | 
 |           .eventid = QFILE_MONITOR_EVENT_ATTRIBUTES }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "two.txt", .watchid = &watch2, | 
 |           .eventid = QFILE_MONITOR_EVENT_ATTRIBUTES }, | 
 |  | 
 |  | 
 |         { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH, | 
 |           .filesrc = "one.txt", .watchid = &watch1 }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH, | 
 |           .filesrc = "one.txt", .watchid = &watch3 }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_CREATE, | 
 |           .filesrc = "one.txt", }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "one.txt", .watchid = &watch0, | 
 |           .eventid = QFILE_MONITOR_EVENT_CREATED }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "one.txt", .watchid = &watch3, | 
 |           .eventid = QFILE_MONITOR_EVENT_CREATED }, | 
 |  | 
 |  | 
 |         { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH, | 
 |           .filesrc = "one.txt", .watchid = &watch3 }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_UNLINK, | 
 |           .filesrc = "one.txt", }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "one.txt", .watchid = &watch0, | 
 |           .eventid = QFILE_MONITOR_EVENT_DELETED }, | 
 |  | 
 |  | 
 |         { .type = QFILE_MONITOR_TEST_OP_MKDIR, | 
 |           .filesrc = "fish", }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "fish", .watchid = &watch0, | 
 |           .eventid = QFILE_MONITOR_EVENT_CREATED }, | 
 |  | 
 |  | 
 |         { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH, | 
 |           .filesrc = "fish/", .watchid = &watch4 }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH, | 
 |           .filesrc = "fish/one.txt", .watchid = &watch5 }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_CREATE, | 
 |           .filesrc = "fish/one.txt", }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "one.txt", .watchid = &watch4, | 
 |           .eventid = QFILE_MONITOR_EVENT_CREATED }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "one.txt", .watchid = &watch5, | 
 |           .eventid = QFILE_MONITOR_EVENT_CREATED }, | 
 |  | 
 |  | 
 |         { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH, | 
 |           .filesrc = "fish/one.txt", .watchid = &watch5 }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_RENAME, | 
 |           .filesrc = "fish/one.txt", .filedst = "two.txt", }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "one.txt", .watchid = &watch4, | 
 |           .eventid = QFILE_MONITOR_EVENT_DELETED }, | 
 | #ifdef __FreeBSD__ | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "two.txt", .watchid = &watch0, | 
 |           .eventid = QFILE_MONITOR_EVENT_DELETED }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "two.txt", .watchid = &watch2, | 
 |           .eventid = QFILE_MONITOR_EVENT_DELETED }, | 
 | #endif | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "two.txt", .watchid = &watch0, | 
 |           .eventid = QFILE_MONITOR_EVENT_CREATED }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "two.txt", .watchid = &watch2, | 
 |           .eventid = QFILE_MONITOR_EVENT_CREATED }, | 
 |  | 
 |  | 
 |         { .type = QFILE_MONITOR_TEST_OP_RMDIR, | 
 |           .filesrc = "fish", }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "", .watchid = &watch4, | 
 |           .eventid = QFILE_MONITOR_EVENT_IGNORED, | 
 |           .swapnext = true }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "fish", .watchid = &watch0, | 
 |           .eventid = QFILE_MONITOR_EVENT_DELETED }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH, | 
 |           .filesrc = "fish", .watchid = &watch4 }, | 
 |  | 
 |  | 
 |         { .type = QFILE_MONITOR_TEST_OP_UNLINK, | 
 |           .filesrc = "two.txt", }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "two.txt", .watchid = &watch0, | 
 |           .eventid = QFILE_MONITOR_EVENT_DELETED }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_EVENT, | 
 |           .filesrc = "two.txt", .watchid = &watch2, | 
 |           .eventid = QFILE_MONITOR_EVENT_DELETED }, | 
 |  | 
 |  | 
 |         { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH, | 
 |           .filesrc = "two.txt", .watchid = &watch2 }, | 
 |         { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH, | 
 |           .filesrc = NULL, .watchid = &watch0 }, | 
 |     }; | 
 |     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; | 
 |     char *pathsrc = NULL; | 
 |     char *pathdst = NULL; | 
 |     QFileMonitorTestData data; | 
 |     GHashTable *ids = g_hash_table_new(g_int64_hash, g_int64_equal); | 
 |     char *travis_arch; | 
 |  | 
 |     qemu_mutex_init(&data.lock); | 
 |     data.records = NULL; | 
 |  | 
 |     /* | 
 |      * This test does not work on Travis LXD containers since some | 
 |      * syscalls are blocked in that environment. | 
 |      */ | 
 |     travis_arch = getenv("TRAVIS_ARCH"); | 
 |     if (travis_arch && !g_str_equal(travis_arch, "x86_64")) { | 
 |         g_test_skip("Test does not work on non-x86 Travis containers."); | 
 |         return; | 
 |     } | 
 |  | 
 |     /* | 
 |      * 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(); | 
 |     } | 
 |  | 
 |     /* | 
 |      * Run through the operation sequence validating events | 
 |      * as we go | 
 |      */ | 
 |     for (i = 0; i < G_N_ELEMENTS(ops); i++) { | 
 |         const QFileMonitorTestOp *op = &(ops[i]); | 
 |         int fd; | 
 |         struct utimbuf ubuf; | 
 |         char *watchdir; | 
 |         const char *watchfile; | 
 |  | 
 |         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_ADD_WATCH: | 
 |             if (debug) { | 
 |                 g_printerr("Add watch %s %s\n", | 
 |                            dir, op->filesrc); | 
 |             } | 
 |             if (op->filesrc && strchr(op->filesrc, '/')) { | 
 |                 watchdir = g_strdup_printf("%s/%s", dir, op->filesrc); | 
 |                 watchfile = strrchr(watchdir, '/'); | 
 |                 *(char *)watchfile = '\0'; | 
 |                 watchfile++; | 
 |                 if (*watchfile == '\0') { | 
 |                     watchfile = NULL; | 
 |                 } | 
 |             } else { | 
 |                 watchdir = g_strdup(dir); | 
 |                 watchfile = op->filesrc; | 
 |             } | 
 |             *op->watchid = | 
 |                 qemu_file_monitor_add_watch(mon, | 
 |                                             watchdir, | 
 |                                             watchfile, | 
 |                                             qemu_file_monitor_test_handler, | 
 |                                             &data, | 
 |                                             &local_err); | 
 |             g_free(watchdir); | 
 |             if (*op->watchid < 0) { | 
 |                 g_printerr("Unable to add watch %s", | 
 |                            error_get_pretty(local_err)); | 
 |                 error_free(local_err); | 
 |                 goto cleanup; | 
 |             } | 
 |             if (debug) { | 
 |                 g_printerr("Watch ID %" PRIx64 "\n", *op->watchid); | 
 |             } | 
 |             if (g_hash_table_contains(ids, op->watchid)) { | 
 |                 g_printerr("Watch ID %" PRIx64 "already exists", *op->watchid); | 
 |                 goto cleanup; | 
 |             } | 
 |             g_hash_table_add(ids, op->watchid); | 
 |             break; | 
 |         case QFILE_MONITOR_TEST_OP_DEL_WATCH: | 
 |             if (debug) { | 
 |                 g_printerr("Del watch %s %" PRIx64 "\n", dir, *op->watchid); | 
 |             } | 
 |             if (op->filesrc && strchr(op->filesrc, '/')) { | 
 |                 watchdir = g_strdup_printf("%s/%s", dir, op->filesrc); | 
 |                 watchfile = strrchr(watchdir, '/'); | 
 |                 *(char *)watchfile = '\0'; | 
 |             } else { | 
 |                 watchdir = g_strdup(dir); | 
 |             } | 
 |             g_hash_table_remove(ids, op->watchid); | 
 |             qemu_file_monitor_remove_watch(mon, | 
 |                                            watchdir, | 
 |                                            *op->watchid); | 
 |             g_free(watchdir); | 
 |             break; | 
 |         case QFILE_MONITOR_TEST_OP_EVENT: | 
 |             if (debug) { | 
 |                 g_printerr("Event id=%" PRIx64 " event=%d file=%s\n", | 
 |                            *op->watchid, op->eventid, op->filesrc); | 
 |             } | 
 |             if (!qemu_file_monitor_test_expect(&data, *op->watchid, | 
 |                                                op->eventid, op->filesrc, | 
 |                                                op->swapnext)) | 
 |                 goto cleanup; | 
 |             break; | 
 |         case QFILE_MONITOR_TEST_OP_CREATE: | 
 |             if (debug) { | 
 |                 g_printerr("Create %s\n", pathsrc); | 
 |             } | 
 |             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: | 
 |             if (debug) { | 
 |                 g_printerr("Append %s\n", pathsrc); | 
 |             } | 
 |             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 (debug) { | 
 |                 g_printerr("Truncate %s\n", pathsrc); | 
 |             } | 
 |             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 (debug) { | 
 |                 g_printerr("Rename %s -> %s\n", pathsrc, pathdst); | 
 |             } | 
 |             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 (debug) { | 
 |                 g_printerr("Unlink %s\n", pathsrc); | 
 |             } | 
 |             if (unlink(pathsrc) < 0) { | 
 |                 g_printerr("Unable to unlink %s: %s", | 
 |                            pathsrc, strerror(errno)); | 
 |                 goto cleanup; | 
 |             } | 
 |             break; | 
 |  | 
 |         case QFILE_MONITOR_TEST_OP_TOUCH: | 
 |             if (debug) { | 
 |                 g_printerr("Touch %s\n", pathsrc); | 
 |             } | 
 |             ubuf.actime = 1024; | 
 |             ubuf.modtime = 1025; | 
 |             if (utime(pathsrc, &ubuf) < 0) { | 
 |                 g_printerr("Unable to touch %s: %s", | 
 |                            pathsrc, strerror(errno)); | 
 |                 goto cleanup; | 
 |             } | 
 |             break; | 
 |  | 
 |         case QFILE_MONITOR_TEST_OP_MKDIR: | 
 |             if (debug) { | 
 |                 g_printerr("Mkdir %s\n", pathsrc); | 
 |             } | 
 |             if (g_mkdir_with_parents(pathsrc, 0700) < 0) { | 
 |                 g_printerr("Unable to mkdir %s: %s", | 
 |                            pathsrc, strerror(errno)); | 
 |                 goto cleanup; | 
 |             } | 
 |             break; | 
 |  | 
 |         case QFILE_MONITOR_TEST_OP_RMDIR: | 
 |             if (debug) { | 
 |                 g_printerr("Rmdir %s\n", pathsrc); | 
 |             } | 
 |             if (rmdir(pathsrc) < 0) { | 
 |                 g_printerr("Unable to rmdir %s: %s", | 
 |                            pathsrc, strerror(errno)); | 
 |                 goto cleanup; | 
 |             } | 
 |             break; | 
 |  | 
 |         default: | 
 |             g_assert_not_reached(); | 
 |         } | 
 |  | 
 |         g_free(pathsrc); | 
 |         g_free(pathdst); | 
 |         pathsrc = pathdst = NULL; | 
 |     } | 
 |  | 
 |     g_assert_cmpint(g_hash_table_size(ids), ==, 0); | 
 |  | 
 |     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); | 
 |  | 
 |     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) { | 
 |         for (i = 0; i < G_N_ELEMENTS(ops); i++) { | 
 |             const QFileMonitorTestOp *op = &(ops[i]); | 
 |             char *path = g_strdup_printf("%s/%s", | 
 |                                          dir, op->filesrc); | 
 |             if (op->type == QFILE_MONITOR_TEST_OP_MKDIR) { | 
 |                 rmdir(path); | 
 |                 g_free(path); | 
 |             } else { | 
 |                 unlink(path); | 
 |                 g_free(path); | 
 |                 if (op->filedst) { | 
 |                     path = g_strdup_printf("%s/%s", | 
 |                                            dir, op->filedst); | 
 |                     unlink(path); | 
 |                     g_free(path); | 
 |                 } | 
 |             } | 
 |         } | 
 |         if (rmdir(dir) < 0) { | 
 |             g_printerr("Failed to remove %s: %s\n", | 
 |                        dir, strerror(errno)); | 
 |             abort(); | 
 |         } | 
 |     } | 
 |     g_hash_table_unref(ids); | 
 |     g_free(dir); | 
 |     g_assert(err == 0); | 
 | } | 
 |  | 
 |  | 
 | int main(int argc, char **argv) | 
 | { | 
 |     g_test_init(&argc, &argv, NULL); | 
 |  | 
 |     qemu_init_main_loop(&error_abort); | 
 |  | 
 |     qemu_mutex_init(&evlock); | 
 |  | 
 |     debug = getenv("FILEMONITOR_DEBUG") != NULL; | 
 |     g_test_add_func("/util/filemonitor", test_file_monitor_events); | 
 |  | 
 |     return g_test_run(); | 
 | } |