filemon: ensure watch IDs are unique to QFileMonitor scope
[qemu/ar7.git] / tests / test-util-filemonitor.c
blob71a7cf5de050fa4726c8f1a79c54dc014377a13c
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 int watchid;
47 int eventid;
48 } QFileMonitorTestOp;
50 typedef struct {
51 int 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(int 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 int 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 %d event %d file %s\n",
170 id, event, filename);
171 return false;
174 if (id != rec->id) {
175 g_printerr("Expected watch id %d but got %d\n", id, rec->id);
176 goto cleanup;
179 if (event != rec->event) {
180 g_printerr("Expected event %d but got %d\n", event, rec->event);
181 goto cleanup;
184 if (!g_str_equal(filename, rec->filename)) {
185 g_printerr("Expected filename %s but got %s\n",
186 filename, rec->filename);
187 goto cleanup;
190 ret = true;
192 cleanup:
193 qemu_file_monitor_test_record_free(rec);
194 return ret;
198 static void
199 test_file_monitor_events(void)
201 QFileMonitorTestOp ops[] = {
202 { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
203 .filesrc = NULL, .watchid = 0 },
204 { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
205 .filesrc = "one.txt", .watchid = 1 },
206 { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
207 .filesrc = "two.txt", .watchid = 2 },
210 { .type = QFILE_MONITOR_TEST_OP_CREATE,
211 .filesrc = "one.txt", },
212 { .type = QFILE_MONITOR_TEST_OP_EVENT,
213 .filesrc = "one.txt", .watchid = 0,
214 .eventid = QFILE_MONITOR_EVENT_CREATED },
215 { .type = QFILE_MONITOR_TEST_OP_EVENT,
216 .filesrc = "one.txt", .watchid = 1,
217 .eventid = QFILE_MONITOR_EVENT_CREATED },
220 { .type = QFILE_MONITOR_TEST_OP_CREATE,
221 .filesrc = "two.txt", },
222 { .type = QFILE_MONITOR_TEST_OP_EVENT,
223 .filesrc = "two.txt", .watchid = 0,
224 .eventid = QFILE_MONITOR_EVENT_CREATED },
225 { .type = QFILE_MONITOR_TEST_OP_EVENT,
226 .filesrc = "two.txt", .watchid = 2,
227 .eventid = QFILE_MONITOR_EVENT_CREATED },
230 { .type = QFILE_MONITOR_TEST_OP_CREATE,
231 .filesrc = "three.txt", },
232 { .type = QFILE_MONITOR_TEST_OP_EVENT,
233 .filesrc = "three.txt", .watchid = 0,
234 .eventid = QFILE_MONITOR_EVENT_CREATED },
237 { .type = QFILE_MONITOR_TEST_OP_UNLINK,
238 .filesrc = "three.txt", },
239 { .type = QFILE_MONITOR_TEST_OP_EVENT,
240 .filesrc = "three.txt", .watchid = 0,
241 .eventid = QFILE_MONITOR_EVENT_DELETED },
244 { .type = QFILE_MONITOR_TEST_OP_RENAME,
245 .filesrc = "one.txt", .filedst = "two.txt" },
246 { .type = QFILE_MONITOR_TEST_OP_EVENT,
247 .filesrc = "one.txt", .watchid = 0,
248 .eventid = QFILE_MONITOR_EVENT_DELETED },
249 { .type = QFILE_MONITOR_TEST_OP_EVENT,
250 .filesrc = "one.txt", .watchid = 1,
251 .eventid = QFILE_MONITOR_EVENT_DELETED },
252 { .type = QFILE_MONITOR_TEST_OP_EVENT,
253 .filesrc = "two.txt", .watchid = 0,
254 .eventid = QFILE_MONITOR_EVENT_CREATED },
255 { .type = QFILE_MONITOR_TEST_OP_EVENT,
256 .filesrc = "two.txt", .watchid = 2,
257 .eventid = QFILE_MONITOR_EVENT_CREATED },
260 { .type = QFILE_MONITOR_TEST_OP_APPEND,
261 .filesrc = "two.txt", },
262 { .type = QFILE_MONITOR_TEST_OP_EVENT,
263 .filesrc = "two.txt", .watchid = 0,
264 .eventid = QFILE_MONITOR_EVENT_MODIFIED },
265 { .type = QFILE_MONITOR_TEST_OP_EVENT,
266 .filesrc = "two.txt", .watchid = 2,
267 .eventid = QFILE_MONITOR_EVENT_MODIFIED },
270 { .type = QFILE_MONITOR_TEST_OP_TOUCH,
271 .filesrc = "two.txt", },
272 { .type = QFILE_MONITOR_TEST_OP_EVENT,
273 .filesrc = "two.txt", .watchid = 0,
274 .eventid = QFILE_MONITOR_EVENT_ATTRIBUTES },
275 { .type = QFILE_MONITOR_TEST_OP_EVENT,
276 .filesrc = "two.txt", .watchid = 2,
277 .eventid = QFILE_MONITOR_EVENT_ATTRIBUTES },
280 { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
281 .filesrc = "one.txt", .watchid = 1 },
282 { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
283 .filesrc = "one.txt", .watchid = 3 },
284 { .type = QFILE_MONITOR_TEST_OP_CREATE,
285 .filesrc = "one.txt", },
286 { .type = QFILE_MONITOR_TEST_OP_EVENT,
287 .filesrc = "one.txt", .watchid = 0,
288 .eventid = QFILE_MONITOR_EVENT_CREATED },
289 { .type = QFILE_MONITOR_TEST_OP_EVENT,
290 .filesrc = "one.txt", .watchid = 3,
291 .eventid = QFILE_MONITOR_EVENT_CREATED },
294 { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
295 .filesrc = "one.txt", .watchid = 3 },
296 { .type = QFILE_MONITOR_TEST_OP_UNLINK,
297 .filesrc = "one.txt", },
298 { .type = QFILE_MONITOR_TEST_OP_EVENT,
299 .filesrc = "one.txt", .watchid = 0,
300 .eventid = QFILE_MONITOR_EVENT_DELETED },
303 { .type = QFILE_MONITOR_TEST_OP_MKDIR,
304 .filesrc = "fish", },
305 { .type = QFILE_MONITOR_TEST_OP_EVENT,
306 .filesrc = "fish", .watchid = 0,
307 .eventid = QFILE_MONITOR_EVENT_CREATED },
310 { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
311 .filesrc = "fish/", .watchid = 4 },
312 { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
313 .filesrc = "fish/one.txt", .watchid = 5 },
314 { .type = QFILE_MONITOR_TEST_OP_CREATE,
315 .filesrc = "fish/one.txt", },
316 { .type = QFILE_MONITOR_TEST_OP_EVENT,
317 .filesrc = "one.txt", .watchid = 4,
318 .eventid = QFILE_MONITOR_EVENT_CREATED },
319 { .type = QFILE_MONITOR_TEST_OP_EVENT,
320 .filesrc = "one.txt", .watchid = 5,
321 .eventid = QFILE_MONITOR_EVENT_CREATED },
324 { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
325 .filesrc = "fish/one.txt", .watchid = 5 },
326 { .type = QFILE_MONITOR_TEST_OP_RENAME,
327 .filesrc = "fish/one.txt", .filedst = "two.txt", },
328 { .type = QFILE_MONITOR_TEST_OP_EVENT,
329 .filesrc = "one.txt", .watchid = 4,
330 .eventid = QFILE_MONITOR_EVENT_DELETED },
331 { .type = QFILE_MONITOR_TEST_OP_EVENT,
332 .filesrc = "two.txt", .watchid = 0,
333 .eventid = QFILE_MONITOR_EVENT_CREATED },
334 { .type = QFILE_MONITOR_TEST_OP_EVENT,
335 .filesrc = "two.txt", .watchid = 2,
336 .eventid = QFILE_MONITOR_EVENT_CREATED },
339 { .type = QFILE_MONITOR_TEST_OP_RMDIR,
340 .filesrc = "fish", },
341 { .type = QFILE_MONITOR_TEST_OP_EVENT,
342 .filesrc = "", .watchid = 4,
343 .eventid = QFILE_MONITOR_EVENT_IGNORED },
344 { .type = QFILE_MONITOR_TEST_OP_EVENT,
345 .filesrc = "fish", .watchid = 0,
346 .eventid = QFILE_MONITOR_EVENT_DELETED },
347 { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
348 .filesrc = "fish", .watchid = 4 },
351 { .type = QFILE_MONITOR_TEST_OP_UNLINK,
352 .filesrc = "two.txt", },
353 { .type = QFILE_MONITOR_TEST_OP_EVENT,
354 .filesrc = "two.txt", .watchid = 0,
355 .eventid = QFILE_MONITOR_EVENT_DELETED },
356 { .type = QFILE_MONITOR_TEST_OP_EVENT,
357 .filesrc = "two.txt", .watchid = 2,
358 .eventid = QFILE_MONITOR_EVENT_DELETED },
361 { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
362 .filesrc = "two.txt", .watchid = 2 },
363 { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
364 .filesrc = NULL, .watchid = 0 },
366 Error *local_err = NULL;
367 GError *gerr = NULL;
368 QFileMonitor *mon = qemu_file_monitor_new(&local_err);
369 QemuThread th;
370 GTimer *timer;
371 gchar *dir = NULL;
372 int err = -1;
373 gsize i;
374 char *pathsrc = NULL;
375 char *pathdst = NULL;
376 QFileMonitorTestData data;
378 qemu_mutex_init(&data.lock);
379 data.records = NULL;
382 * The file monitor needs the main loop running in
383 * order to receive events from inotify. We must
384 * thus spawn a background thread to run an event
385 * loop impl, while this thread triggers the
386 * actual file operations we're testing
388 evrunning = 1;
389 evstopping = 0;
390 qemu_thread_create(&th, "event-loop",
391 qemu_file_monitor_test_event_loop, NULL,
392 QEMU_THREAD_JOINABLE);
394 if (local_err) {
395 g_printerr("File monitoring not available: %s",
396 error_get_pretty(local_err));
397 error_free(local_err);
398 return;
401 dir = g_dir_make_tmp("test-util-filemonitor-XXXXXX",
402 &gerr);
403 if (!dir) {
404 g_printerr("Unable to create tmp dir %s",
405 gerr->message);
406 g_error_free(gerr);
407 abort();
411 * Run through the operation sequence validating events
412 * as we go
414 for (i = 0; i < G_N_ELEMENTS(ops); i++) {
415 const QFileMonitorTestOp *op = &(ops[i]);
416 int fd;
417 int watchid;
418 struct utimbuf ubuf;
419 char *watchdir;
420 const char *watchfile;
422 pathsrc = g_strdup_printf("%s/%s", dir, op->filesrc);
423 if (op->filedst) {
424 pathdst = g_strdup_printf("%s/%s", dir, op->filedst);
427 switch (op->type) {
428 case QFILE_MONITOR_TEST_OP_ADD_WATCH:
429 if (debug) {
430 g_printerr("Add watch %s %s %d\n",
431 dir, op->filesrc, op->watchid);
433 if (op->filesrc && strchr(op->filesrc, '/')) {
434 watchdir = g_strdup_printf("%s/%s", dir, op->filesrc);
435 watchfile = strrchr(watchdir, '/');
436 *(char *)watchfile = '\0';
437 watchfile++;
438 if (*watchfile == '\0') {
439 watchfile = NULL;
441 } else {
442 watchdir = g_strdup(dir);
443 watchfile = op->filesrc;
445 watchid =
446 qemu_file_monitor_add_watch(mon,
447 watchdir,
448 watchfile,
449 qemu_file_monitor_test_handler,
450 &data,
451 &local_err);
452 g_free(watchdir);
453 if (watchid < 0) {
454 g_printerr("Unable to add watch %s",
455 error_get_pretty(local_err));
456 goto cleanup;
458 if (watchid != op->watchid) {
459 g_printerr("Unexpected watch ID %d, wanted %d\n",
460 watchid, op->watchid);
461 goto cleanup;
463 break;
464 case QFILE_MONITOR_TEST_OP_DEL_WATCH:
465 if (debug) {
466 g_printerr("Del watch %s %d\n", dir, op->watchid);
468 if (op->filesrc && strchr(op->filesrc, '/')) {
469 watchdir = g_strdup_printf("%s/%s", dir, op->filesrc);
470 watchfile = strrchr(watchdir, '/');
471 *(char *)watchfile = '\0';
472 } else {
473 watchdir = g_strdup(dir);
475 qemu_file_monitor_remove_watch(mon,
476 watchdir,
477 op->watchid);
478 g_free(watchdir);
479 break;
480 case QFILE_MONITOR_TEST_OP_EVENT:
481 if (debug) {
482 g_printerr("Event id=%d event=%d file=%s\n",
483 op->watchid, op->eventid, op->filesrc);
485 if (!qemu_file_monitor_test_expect(
486 &data, op->watchid, op->eventid, op->filesrc))
487 goto cleanup;
488 break;
489 case QFILE_MONITOR_TEST_OP_CREATE:
490 if (debug) {
491 g_printerr("Create %s\n", pathsrc);
493 fd = open(pathsrc, O_WRONLY | O_CREAT, 0700);
494 if (fd < 0) {
495 g_printerr("Unable to create %s: %s",
496 pathsrc, strerror(errno));
497 goto cleanup;
499 close(fd);
500 break;
502 case QFILE_MONITOR_TEST_OP_APPEND:
503 if (debug) {
504 g_printerr("Append %s\n", pathsrc);
506 fd = open(pathsrc, O_WRONLY | O_APPEND, 0700);
507 if (fd < 0) {
508 g_printerr("Unable to open %s: %s",
509 pathsrc, strerror(errno));
510 goto cleanup;
513 if (write(fd, "Hello World", 10) != 10) {
514 g_printerr("Unable to write %s: %s",
515 pathsrc, strerror(errno));
516 close(fd);
517 goto cleanup;
519 close(fd);
520 break;
522 case QFILE_MONITOR_TEST_OP_TRUNC:
523 if (debug) {
524 g_printerr("Truncate %s\n", pathsrc);
526 if (truncate(pathsrc, 4) < 0) {
527 g_printerr("Unable to truncate %s: %s",
528 pathsrc, strerror(errno));
529 goto cleanup;
531 break;
533 case QFILE_MONITOR_TEST_OP_RENAME:
534 if (debug) {
535 g_printerr("Rename %s -> %s\n", pathsrc, pathdst);
537 if (rename(pathsrc, pathdst) < 0) {
538 g_printerr("Unable to rename %s to %s: %s",
539 pathsrc, pathdst, strerror(errno));
540 goto cleanup;
542 break;
544 case QFILE_MONITOR_TEST_OP_UNLINK:
545 if (debug) {
546 g_printerr("Unlink %s\n", pathsrc);
548 if (unlink(pathsrc) < 0) {
549 g_printerr("Unable to unlink %s: %s",
550 pathsrc, strerror(errno));
551 goto cleanup;
553 break;
555 case QFILE_MONITOR_TEST_OP_TOUCH:
556 if (debug) {
557 g_printerr("Touch %s\n", pathsrc);
559 ubuf.actime = 1024;
560 ubuf.modtime = 1025;
561 if (utime(pathsrc, &ubuf) < 0) {
562 g_printerr("Unable to touch %s: %s",
563 pathsrc, strerror(errno));
564 goto cleanup;
566 break;
568 case QFILE_MONITOR_TEST_OP_MKDIR:
569 if (debug) {
570 g_printerr("Mkdir %s\n", pathsrc);
572 if (mkdir(pathsrc, 0700) < 0) {
573 g_printerr("Unable to mkdir %s: %s",
574 pathsrc, strerror(errno));
575 goto cleanup;
577 break;
579 case QFILE_MONITOR_TEST_OP_RMDIR:
580 if (debug) {
581 g_printerr("Rmdir %s\n", pathsrc);
583 if (rmdir(pathsrc) < 0) {
584 g_printerr("Unable to rmdir %s: %s",
585 pathsrc, strerror(errno));
586 goto cleanup;
588 break;
590 default:
591 g_assert_not_reached();
594 g_free(pathsrc);
595 g_free(pathdst);
596 pathsrc = pathdst = NULL;
599 err = 0;
601 cleanup:
602 g_free(pathsrc);
603 g_free(pathdst);
605 qemu_mutex_lock(&evlock);
606 evstopping = 1;
607 timer = g_timer_new();
608 while (evrunning && g_timer_elapsed(timer, NULL) < 5) {
609 qemu_mutex_unlock(&evlock);
610 usleep(10 * 1000);
611 qemu_mutex_lock(&evlock);
613 qemu_mutex_unlock(&evlock);
615 if (g_timer_elapsed(timer, NULL) >= 5) {
616 g_printerr("Event loop failed to quit after 5 seconds\n");
618 g_timer_destroy(timer);
620 qemu_file_monitor_free(mon);
621 g_list_foreach(data.records,
622 (GFunc)qemu_file_monitor_test_record_free, NULL);
623 g_list_free(data.records);
624 qemu_mutex_destroy(&data.lock);
625 if (dir) {
626 for (i = 0; i < G_N_ELEMENTS(ops); i++) {
627 const QFileMonitorTestOp *op = &(ops[i]);
628 char *path = g_strdup_printf("%s/%s",
629 dir, op->filesrc);
630 if (op->type == QFILE_MONITOR_TEST_OP_MKDIR) {
631 rmdir(path);
632 g_free(path);
633 } else {
634 unlink(path);
635 g_free(path);
636 if (op->filedst) {
637 path = g_strdup_printf("%s/%s",
638 dir, op->filedst);
639 unlink(path);
640 g_free(path);
644 if (rmdir(dir) < 0) {
645 g_printerr("Failed to remove %s: %s\n",
646 dir, strerror(errno));
647 abort();
650 g_free(dir);
651 g_assert(err == 0);
655 int main(int argc, char **argv)
657 g_test_init(&argc, &argv, NULL);
659 qemu_init_main_loop(&error_abort);
661 qemu_mutex_init(&evlock);
663 debug = getenv("FILEMONITOR_DEBUG") != NULL;
664 g_test_add_func("/util/filemonitor", test_file_monitor_events);
666 return g_test_run();