Merge remote-tracking branch 'remotes/ehabkost/tags/x86-next-pull-request' into staging
[qemu/ar7.git] / tests / test-util-filemonitor.c
blob46e781c022310c496b81ff9ecf8856cc3ac3cc8e
1 /*
2 * Tests for util/filemonitor-*.c
4 * Copyright 2018 Red Hat, Inc.
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this library; if not, see <http://www.gnu.org/licenses/>.
21 #include "qemu/osdep.h"
22 #include "qemu/main-loop.h"
23 #include "qapi/error.h"
24 #include "qemu/filemonitor.h"
26 #include <utime.h>
28 enum {
29 QFILE_MONITOR_TEST_OP_ADD_WATCH,
30 QFILE_MONITOR_TEST_OP_DEL_WATCH,
31 QFILE_MONITOR_TEST_OP_EVENT,
32 QFILE_MONITOR_TEST_OP_CREATE,
33 QFILE_MONITOR_TEST_OP_APPEND,
34 QFILE_MONITOR_TEST_OP_TRUNC,
35 QFILE_MONITOR_TEST_OP_RENAME,
36 QFILE_MONITOR_TEST_OP_TOUCH,
37 QFILE_MONITOR_TEST_OP_UNLINK,
38 QFILE_MONITOR_TEST_OP_MKDIR,
39 QFILE_MONITOR_TEST_OP_RMDIR,
42 typedef struct {
43 int type;
44 const char *filesrc;
45 const char *filedst;
46 int64_t *watchid;
47 int eventid;
48 } QFileMonitorTestOp;
50 typedef struct {
51 int64_t id;
52 QFileMonitorEvent event;
53 char *filename;
54 } QFileMonitorTestRecord;
57 typedef struct {
58 QemuMutex lock;
59 GList *records;
60 } QFileMonitorTestData;
62 static QemuMutex evlock;
63 static bool evstopping;
64 static bool evrunning;
65 static bool debug;
68 * Main function for a background thread that is
69 * running the event loop during the test
71 static void *
72 qemu_file_monitor_test_event_loop(void *opaque G_GNUC_UNUSED)
74 qemu_mutex_lock(&evlock);
76 while (!evstopping) {
77 qemu_mutex_unlock(&evlock);
78 main_loop_wait(true);
79 qemu_mutex_lock(&evlock);
82 evrunning = false;
83 qemu_mutex_unlock(&evlock);
84 return NULL;
89 * File monitor event handler which simply maintains
90 * an ordered list of all events that it receives
92 static void
93 qemu_file_monitor_test_handler(int64_t id,
94 QFileMonitorEvent event,
95 const char *filename,
96 void *opaque)
98 QFileMonitorTestData *data = opaque;
99 QFileMonitorTestRecord *rec = g_new0(QFileMonitorTestRecord, 1);
101 rec->id = id;
102 rec->event = event;
103 rec->filename = g_strdup(filename);
105 qemu_mutex_lock(&data->lock);
106 data->records = g_list_append(data->records, rec);
107 qemu_mutex_unlock(&data->lock);
111 static void
112 qemu_file_monitor_test_record_free(QFileMonitorTestRecord *rec)
114 g_free(rec->filename);
115 g_free(rec);
120 * Get the next event record that has been received by
121 * the file monitor event handler. Since events are
122 * emitted in the background thread running the event
123 * loop, we can't assume there is a record available
124 * immediately. Thus we will sleep for upto 5 seconds
125 * to wait for the event to be queued for us.
127 static QFileMonitorTestRecord *
128 qemu_file_monitor_test_next_record(QFileMonitorTestData *data)
130 GTimer *timer = g_timer_new();
131 QFileMonitorTestRecord *record = NULL;
132 GList *tmp;
134 qemu_mutex_lock(&data->lock);
135 while (!data->records && g_timer_elapsed(timer, NULL) < 5) {
136 qemu_mutex_unlock(&data->lock);
137 usleep(10 * 1000);
138 qemu_mutex_lock(&data->lock);
140 if (data->records) {
141 record = data->records->data;
142 tmp = data->records;
143 data->records = g_list_remove_link(data->records, tmp);
144 g_list_free(tmp);
146 qemu_mutex_unlock(&data->lock);
148 g_timer_destroy(timer);
149 return record;
154 * Check whether the event record we retrieved matches
155 * data we were expecting to see for the event
157 static bool
158 qemu_file_monitor_test_expect(QFileMonitorTestData *data,
159 int64_t id,
160 QFileMonitorEvent event,
161 const char *filename)
163 QFileMonitorTestRecord *rec;
164 bool ret = false;
166 rec = qemu_file_monitor_test_next_record(data);
168 if (!rec) {
169 g_printerr("Missing event watch id %" PRIx64 " event %d file %s\n",
170 id, event, filename);
171 return false;
174 if (id != rec->id) {
175 g_printerr("Expected watch id %" PRIx64 " but got %" PRIx64 "\n",
176 id, rec->id);
177 goto cleanup;
180 if (event != rec->event) {
181 g_printerr("Expected event %d but got %d\n", event, rec->event);
182 goto cleanup;
185 if (!g_str_equal(filename, rec->filename)) {
186 g_printerr("Expected filename %s but got %s\n",
187 filename, rec->filename);
188 goto cleanup;
191 ret = true;
193 cleanup:
194 qemu_file_monitor_test_record_free(rec);
195 return ret;
199 static void
200 test_file_monitor_events(void)
202 int64_t watch0 = 0;
203 int64_t watch1 = 0;
204 int64_t watch2 = 0;
205 int64_t watch3 = 0;
206 int64_t watch4 = 0;
207 int64_t watch5 = 0;
208 QFileMonitorTestOp ops[] = {
209 { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
210 .filesrc = NULL, .watchid = &watch0 },
211 { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
212 .filesrc = "one.txt", .watchid = &watch1 },
213 { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
214 .filesrc = "two.txt", .watchid = &watch2 },
217 { .type = QFILE_MONITOR_TEST_OP_CREATE,
218 .filesrc = "one.txt", },
219 { .type = QFILE_MONITOR_TEST_OP_EVENT,
220 .filesrc = "one.txt", .watchid = &watch0,
221 .eventid = QFILE_MONITOR_EVENT_CREATED },
222 { .type = QFILE_MONITOR_TEST_OP_EVENT,
223 .filesrc = "one.txt", .watchid = &watch1,
224 .eventid = QFILE_MONITOR_EVENT_CREATED },
227 { .type = QFILE_MONITOR_TEST_OP_CREATE,
228 .filesrc = "two.txt", },
229 { .type = QFILE_MONITOR_TEST_OP_EVENT,
230 .filesrc = "two.txt", .watchid = &watch0,
231 .eventid = QFILE_MONITOR_EVENT_CREATED },
232 { .type = QFILE_MONITOR_TEST_OP_EVENT,
233 .filesrc = "two.txt", .watchid = &watch2,
234 .eventid = QFILE_MONITOR_EVENT_CREATED },
237 { .type = QFILE_MONITOR_TEST_OP_CREATE,
238 .filesrc = "three.txt", },
239 { .type = QFILE_MONITOR_TEST_OP_EVENT,
240 .filesrc = "three.txt", .watchid = &watch0,
241 .eventid = QFILE_MONITOR_EVENT_CREATED },
244 { .type = QFILE_MONITOR_TEST_OP_UNLINK,
245 .filesrc = "three.txt", },
246 { .type = QFILE_MONITOR_TEST_OP_EVENT,
247 .filesrc = "three.txt", .watchid = &watch0,
248 .eventid = QFILE_MONITOR_EVENT_DELETED },
251 { .type = QFILE_MONITOR_TEST_OP_RENAME,
252 .filesrc = "one.txt", .filedst = "two.txt" },
253 { .type = QFILE_MONITOR_TEST_OP_EVENT,
254 .filesrc = "one.txt", .watchid = &watch0,
255 .eventid = QFILE_MONITOR_EVENT_DELETED },
256 { .type = QFILE_MONITOR_TEST_OP_EVENT,
257 .filesrc = "one.txt", .watchid = &watch1,
258 .eventid = QFILE_MONITOR_EVENT_DELETED },
259 { .type = QFILE_MONITOR_TEST_OP_EVENT,
260 .filesrc = "two.txt", .watchid = &watch0,
261 .eventid = QFILE_MONITOR_EVENT_CREATED },
262 { .type = QFILE_MONITOR_TEST_OP_EVENT,
263 .filesrc = "two.txt", .watchid = &watch2,
264 .eventid = QFILE_MONITOR_EVENT_CREATED },
267 { .type = QFILE_MONITOR_TEST_OP_APPEND,
268 .filesrc = "two.txt", },
269 { .type = QFILE_MONITOR_TEST_OP_EVENT,
270 .filesrc = "two.txt", .watchid = &watch0,
271 .eventid = QFILE_MONITOR_EVENT_MODIFIED },
272 { .type = QFILE_MONITOR_TEST_OP_EVENT,
273 .filesrc = "two.txt", .watchid = &watch2,
274 .eventid = QFILE_MONITOR_EVENT_MODIFIED },
277 { .type = QFILE_MONITOR_TEST_OP_TOUCH,
278 .filesrc = "two.txt", },
279 { .type = QFILE_MONITOR_TEST_OP_EVENT,
280 .filesrc = "two.txt", .watchid = &watch0,
281 .eventid = QFILE_MONITOR_EVENT_ATTRIBUTES },
282 { .type = QFILE_MONITOR_TEST_OP_EVENT,
283 .filesrc = "two.txt", .watchid = &watch2,
284 .eventid = QFILE_MONITOR_EVENT_ATTRIBUTES },
287 { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
288 .filesrc = "one.txt", .watchid = &watch1 },
289 { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
290 .filesrc = "one.txt", .watchid = &watch3 },
291 { .type = QFILE_MONITOR_TEST_OP_CREATE,
292 .filesrc = "one.txt", },
293 { .type = QFILE_MONITOR_TEST_OP_EVENT,
294 .filesrc = "one.txt", .watchid = &watch0,
295 .eventid = QFILE_MONITOR_EVENT_CREATED },
296 { .type = QFILE_MONITOR_TEST_OP_EVENT,
297 .filesrc = "one.txt", .watchid = &watch3,
298 .eventid = QFILE_MONITOR_EVENT_CREATED },
301 { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
302 .filesrc = "one.txt", .watchid = &watch3 },
303 { .type = QFILE_MONITOR_TEST_OP_UNLINK,
304 .filesrc = "one.txt", },
305 { .type = QFILE_MONITOR_TEST_OP_EVENT,
306 .filesrc = "one.txt", .watchid = &watch0,
307 .eventid = QFILE_MONITOR_EVENT_DELETED },
310 { .type = QFILE_MONITOR_TEST_OP_MKDIR,
311 .filesrc = "fish", },
312 { .type = QFILE_MONITOR_TEST_OP_EVENT,
313 .filesrc = "fish", .watchid = &watch0,
314 .eventid = QFILE_MONITOR_EVENT_CREATED },
317 { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
318 .filesrc = "fish/", .watchid = &watch4 },
319 { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
320 .filesrc = "fish/one.txt", .watchid = &watch5 },
321 { .type = QFILE_MONITOR_TEST_OP_CREATE,
322 .filesrc = "fish/one.txt", },
323 { .type = QFILE_MONITOR_TEST_OP_EVENT,
324 .filesrc = "one.txt", .watchid = &watch4,
325 .eventid = QFILE_MONITOR_EVENT_CREATED },
326 { .type = QFILE_MONITOR_TEST_OP_EVENT,
327 .filesrc = "one.txt", .watchid = &watch5,
328 .eventid = QFILE_MONITOR_EVENT_CREATED },
331 { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
332 .filesrc = "fish/one.txt", .watchid = &watch5 },
333 { .type = QFILE_MONITOR_TEST_OP_RENAME,
334 .filesrc = "fish/one.txt", .filedst = "two.txt", },
335 { .type = QFILE_MONITOR_TEST_OP_EVENT,
336 .filesrc = "one.txt", .watchid = &watch4,
337 .eventid = QFILE_MONITOR_EVENT_DELETED },
338 { .type = QFILE_MONITOR_TEST_OP_EVENT,
339 .filesrc = "two.txt", .watchid = &watch0,
340 .eventid = QFILE_MONITOR_EVENT_CREATED },
341 { .type = QFILE_MONITOR_TEST_OP_EVENT,
342 .filesrc = "two.txt", .watchid = &watch2,
343 .eventid = QFILE_MONITOR_EVENT_CREATED },
346 { .type = QFILE_MONITOR_TEST_OP_RMDIR,
347 .filesrc = "fish", },
348 { .type = QFILE_MONITOR_TEST_OP_EVENT,
349 .filesrc = "", .watchid = &watch4,
350 .eventid = QFILE_MONITOR_EVENT_IGNORED },
351 { .type = QFILE_MONITOR_TEST_OP_EVENT,
352 .filesrc = "fish", .watchid = &watch0,
353 .eventid = QFILE_MONITOR_EVENT_DELETED },
354 { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
355 .filesrc = "fish", .watchid = &watch4 },
358 { .type = QFILE_MONITOR_TEST_OP_UNLINK,
359 .filesrc = "two.txt", },
360 { .type = QFILE_MONITOR_TEST_OP_EVENT,
361 .filesrc = "two.txt", .watchid = &watch0,
362 .eventid = QFILE_MONITOR_EVENT_DELETED },
363 { .type = QFILE_MONITOR_TEST_OP_EVENT,
364 .filesrc = "two.txt", .watchid = &watch2,
365 .eventid = QFILE_MONITOR_EVENT_DELETED },
368 { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
369 .filesrc = "two.txt", .watchid = &watch2 },
370 { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
371 .filesrc = NULL, .watchid = &watch0 },
373 Error *local_err = NULL;
374 GError *gerr = NULL;
375 QFileMonitor *mon = qemu_file_monitor_new(&local_err);
376 QemuThread th;
377 GTimer *timer;
378 gchar *dir = NULL;
379 int err = -1;
380 gsize i;
381 char *pathsrc = NULL;
382 char *pathdst = NULL;
383 QFileMonitorTestData data;
384 GHashTable *ids = g_hash_table_new(g_int64_hash, g_int64_equal);
386 qemu_mutex_init(&data.lock);
387 data.records = NULL;
390 * The file monitor needs the main loop running in
391 * order to receive events from inotify. We must
392 * thus spawn a background thread to run an event
393 * loop impl, while this thread triggers the
394 * actual file operations we're testing
396 evrunning = 1;
397 evstopping = 0;
398 qemu_thread_create(&th, "event-loop",
399 qemu_file_monitor_test_event_loop, NULL,
400 QEMU_THREAD_JOINABLE);
402 if (local_err) {
403 g_printerr("File monitoring not available: %s",
404 error_get_pretty(local_err));
405 error_free(local_err);
406 return;
409 dir = g_dir_make_tmp("test-util-filemonitor-XXXXXX",
410 &gerr);
411 if (!dir) {
412 g_printerr("Unable to create tmp dir %s",
413 gerr->message);
414 g_error_free(gerr);
415 abort();
419 * Run through the operation sequence validating events
420 * as we go
422 for (i = 0; i < G_N_ELEMENTS(ops); i++) {
423 const QFileMonitorTestOp *op = &(ops[i]);
424 int fd;
425 struct utimbuf ubuf;
426 char *watchdir;
427 const char *watchfile;
429 pathsrc = g_strdup_printf("%s/%s", dir, op->filesrc);
430 if (op->filedst) {
431 pathdst = g_strdup_printf("%s/%s", dir, op->filedst);
434 switch (op->type) {
435 case QFILE_MONITOR_TEST_OP_ADD_WATCH:
436 if (debug) {
437 g_printerr("Add watch %s %s\n",
438 dir, op->filesrc);
440 if (op->filesrc && strchr(op->filesrc, '/')) {
441 watchdir = g_strdup_printf("%s/%s", dir, op->filesrc);
442 watchfile = strrchr(watchdir, '/');
443 *(char *)watchfile = '\0';
444 watchfile++;
445 if (*watchfile == '\0') {
446 watchfile = NULL;
448 } else {
449 watchdir = g_strdup(dir);
450 watchfile = op->filesrc;
452 *op->watchid =
453 qemu_file_monitor_add_watch(mon,
454 watchdir,
455 watchfile,
456 qemu_file_monitor_test_handler,
457 &data,
458 &local_err);
459 g_free(watchdir);
460 if (*op->watchid < 0) {
461 g_printerr("Unable to add watch %s",
462 error_get_pretty(local_err));
463 goto cleanup;
465 if (debug) {
466 g_printerr("Watch ID %" PRIx64 "\n", *op->watchid);
468 if (g_hash_table_contains(ids, op->watchid)) {
469 g_printerr("Watch ID %" PRIx64 "already exists", *op->watchid);
470 goto cleanup;
472 g_hash_table_add(ids, op->watchid);
473 break;
474 case QFILE_MONITOR_TEST_OP_DEL_WATCH:
475 if (debug) {
476 g_printerr("Del watch %s %" PRIx64 "\n", dir, *op->watchid);
478 if (op->filesrc && strchr(op->filesrc, '/')) {
479 watchdir = g_strdup_printf("%s/%s", dir, op->filesrc);
480 watchfile = strrchr(watchdir, '/');
481 *(char *)watchfile = '\0';
482 } else {
483 watchdir = g_strdup(dir);
485 g_hash_table_remove(ids, op->watchid);
486 qemu_file_monitor_remove_watch(mon,
487 watchdir,
488 *op->watchid);
489 g_free(watchdir);
490 break;
491 case QFILE_MONITOR_TEST_OP_EVENT:
492 if (debug) {
493 g_printerr("Event id=%" PRIx64 " event=%d file=%s\n",
494 *op->watchid, op->eventid, op->filesrc);
496 if (!qemu_file_monitor_test_expect(
497 &data, *op->watchid, op->eventid, op->filesrc))
498 goto cleanup;
499 break;
500 case QFILE_MONITOR_TEST_OP_CREATE:
501 if (debug) {
502 g_printerr("Create %s\n", pathsrc);
504 fd = open(pathsrc, O_WRONLY | O_CREAT, 0700);
505 if (fd < 0) {
506 g_printerr("Unable to create %s: %s",
507 pathsrc, strerror(errno));
508 goto cleanup;
510 close(fd);
511 break;
513 case QFILE_MONITOR_TEST_OP_APPEND:
514 if (debug) {
515 g_printerr("Append %s\n", pathsrc);
517 fd = open(pathsrc, O_WRONLY | O_APPEND, 0700);
518 if (fd < 0) {
519 g_printerr("Unable to open %s: %s",
520 pathsrc, strerror(errno));
521 goto cleanup;
524 if (write(fd, "Hello World", 10) != 10) {
525 g_printerr("Unable to write %s: %s",
526 pathsrc, strerror(errno));
527 close(fd);
528 goto cleanup;
530 close(fd);
531 break;
533 case QFILE_MONITOR_TEST_OP_TRUNC:
534 if (debug) {
535 g_printerr("Truncate %s\n", pathsrc);
537 if (truncate(pathsrc, 4) < 0) {
538 g_printerr("Unable to truncate %s: %s",
539 pathsrc, strerror(errno));
540 goto cleanup;
542 break;
544 case QFILE_MONITOR_TEST_OP_RENAME:
545 if (debug) {
546 g_printerr("Rename %s -> %s\n", pathsrc, pathdst);
548 if (rename(pathsrc, pathdst) < 0) {
549 g_printerr("Unable to rename %s to %s: %s",
550 pathsrc, pathdst, strerror(errno));
551 goto cleanup;
553 break;
555 case QFILE_MONITOR_TEST_OP_UNLINK:
556 if (debug) {
557 g_printerr("Unlink %s\n", pathsrc);
559 if (unlink(pathsrc) < 0) {
560 g_printerr("Unable to unlink %s: %s",
561 pathsrc, strerror(errno));
562 goto cleanup;
564 break;
566 case QFILE_MONITOR_TEST_OP_TOUCH:
567 if (debug) {
568 g_printerr("Touch %s\n", pathsrc);
570 ubuf.actime = 1024;
571 ubuf.modtime = 1025;
572 if (utime(pathsrc, &ubuf) < 0) {
573 g_printerr("Unable to touch %s: %s",
574 pathsrc, strerror(errno));
575 goto cleanup;
577 break;
579 case QFILE_MONITOR_TEST_OP_MKDIR:
580 if (debug) {
581 g_printerr("Mkdir %s\n", pathsrc);
583 if (mkdir(pathsrc, 0700) < 0) {
584 g_printerr("Unable to mkdir %s: %s",
585 pathsrc, strerror(errno));
586 goto cleanup;
588 break;
590 case QFILE_MONITOR_TEST_OP_RMDIR:
591 if (debug) {
592 g_printerr("Rmdir %s\n", pathsrc);
594 if (rmdir(pathsrc) < 0) {
595 g_printerr("Unable to rmdir %s: %s",
596 pathsrc, strerror(errno));
597 goto cleanup;
599 break;
601 default:
602 g_assert_not_reached();
605 g_free(pathsrc);
606 g_free(pathdst);
607 pathsrc = pathdst = NULL;
610 g_assert_cmpint(g_hash_table_size(ids), ==, 0);
612 err = 0;
614 cleanup:
615 g_free(pathsrc);
616 g_free(pathdst);
618 qemu_mutex_lock(&evlock);
619 evstopping = 1;
620 timer = g_timer_new();
621 while (evrunning && g_timer_elapsed(timer, NULL) < 5) {
622 qemu_mutex_unlock(&evlock);
623 usleep(10 * 1000);
624 qemu_mutex_lock(&evlock);
626 qemu_mutex_unlock(&evlock);
628 if (g_timer_elapsed(timer, NULL) >= 5) {
629 g_printerr("Event loop failed to quit after 5 seconds\n");
631 g_timer_destroy(timer);
633 qemu_file_monitor_free(mon);
634 g_list_foreach(data.records,
635 (GFunc)qemu_file_monitor_test_record_free, NULL);
636 g_list_free(data.records);
637 qemu_mutex_destroy(&data.lock);
638 if (dir) {
639 for (i = 0; i < G_N_ELEMENTS(ops); i++) {
640 const QFileMonitorTestOp *op = &(ops[i]);
641 char *path = g_strdup_printf("%s/%s",
642 dir, op->filesrc);
643 if (op->type == QFILE_MONITOR_TEST_OP_MKDIR) {
644 rmdir(path);
645 g_free(path);
646 } else {
647 unlink(path);
648 g_free(path);
649 if (op->filedst) {
650 path = g_strdup_printf("%s/%s",
651 dir, op->filedst);
652 unlink(path);
653 g_free(path);
657 if (rmdir(dir) < 0) {
658 g_printerr("Failed to remove %s: %s\n",
659 dir, strerror(errno));
660 abort();
663 g_hash_table_unref(ids);
664 g_free(dir);
665 g_assert(err == 0);
669 int main(int argc, char **argv)
671 g_test_init(&argc, &argv, NULL);
673 qemu_init_main_loop(&error_abort);
675 qemu_mutex_init(&evlock);
677 debug = getenv("FILEMONITOR_DEBUG") != NULL;
678 g_test_add_func("/util/filemonitor", test_file_monitor_events);
680 return g_test_run();