qemu/tests/vhost-user-test.c
<<
>>
Prefs
   1/*
   2 * QTest testcase for the vhost-user
   3 *
   4 * Copyright (c) 2014 Virtual Open Systems Sarl.
   5 *
   6 * This work is licensed under the terms of the GNU GPL, version 2 or later.
   7 * See the COPYING file in the top-level directory.
   8 *
   9 */
  10
  11#include "qemu/osdep.h"
  12#include <glib.h>
  13
  14#include "libqtest.h"
  15#include "qemu/option.h"
  16#include "qemu/range.h"
  17#include "sysemu/char.h"
  18#include "sysemu/sysemu.h"
  19
  20#include <linux/vhost.h>
  21#include <sys/mman.h>
  22#include <sys/vfs.h>
  23#include <qemu/sockets.h>
  24
  25/* GLIB version compatibility flags */
  26#if !GLIB_CHECK_VERSION(2, 26, 0)
  27#define G_TIME_SPAN_SECOND              (G_GINT64_CONSTANT(1000000))
  28#endif
  29
  30#if GLIB_CHECK_VERSION(2, 28, 0)
  31#define HAVE_MONOTONIC_TIME
  32#endif
  33
  34#define QEMU_CMD_ACCEL  " -machine accel=tcg"
  35#define QEMU_CMD_MEM    " -m %d -object memory-backend-file,id=mem,size=%dM,"\
  36                        "mem-path=%s,share=on -numa node,memdev=mem"
  37#define QEMU_CMD_CHR    " -chardev socket,id=%s,path=%s"
  38#define QEMU_CMD_NETDEV " -netdev vhost-user,id=net0,chardev=%s,vhostforce"
  39#define QEMU_CMD_NET    " -device virtio-net-pci,netdev=net0,romfile=./pc-bios/pxe-virtio.rom"
  40
  41#define QEMU_CMD        QEMU_CMD_ACCEL QEMU_CMD_MEM QEMU_CMD_CHR \
  42                        QEMU_CMD_NETDEV QEMU_CMD_NET
  43
  44#define HUGETLBFS_MAGIC       0x958458f6
  45
  46/*********** FROM hw/virtio/vhost-user.c *************************************/
  47
  48#define VHOST_MEMORY_MAX_NREGIONS    8
  49
  50#define VHOST_USER_F_PROTOCOL_FEATURES 30
  51#define VHOST_USER_PROTOCOL_F_LOG_SHMFD 1
  52
  53#define VHOST_LOG_PAGE 0x1000
  54
  55typedef enum VhostUserRequest {
  56    VHOST_USER_NONE = 0,
  57    VHOST_USER_GET_FEATURES = 1,
  58    VHOST_USER_SET_FEATURES = 2,
  59    VHOST_USER_SET_OWNER = 3,
  60    VHOST_USER_RESET_OWNER = 4,
  61    VHOST_USER_SET_MEM_TABLE = 5,
  62    VHOST_USER_SET_LOG_BASE = 6,
  63    VHOST_USER_SET_LOG_FD = 7,
  64    VHOST_USER_SET_VRING_NUM = 8,
  65    VHOST_USER_SET_VRING_ADDR = 9,
  66    VHOST_USER_SET_VRING_BASE = 10,
  67    VHOST_USER_GET_VRING_BASE = 11,
  68    VHOST_USER_SET_VRING_KICK = 12,
  69    VHOST_USER_SET_VRING_CALL = 13,
  70    VHOST_USER_SET_VRING_ERR = 14,
  71    VHOST_USER_GET_PROTOCOL_FEATURES = 15,
  72    VHOST_USER_SET_PROTOCOL_FEATURES = 16,
  73    VHOST_USER_SET_VRING_ENABLE = 18,
  74    VHOST_USER_MAX
  75} VhostUserRequest;
  76
  77typedef struct VhostUserMemoryRegion {
  78    uint64_t guest_phys_addr;
  79    uint64_t memory_size;
  80    uint64_t userspace_addr;
  81    uint64_t mmap_offset;
  82} VhostUserMemoryRegion;
  83
  84typedef struct VhostUserMemory {
  85    uint32_t nregions;
  86    uint32_t padding;
  87    VhostUserMemoryRegion regions[VHOST_MEMORY_MAX_NREGIONS];
  88} VhostUserMemory;
  89
  90typedef struct VhostUserLog {
  91    uint64_t mmap_size;
  92    uint64_t mmap_offset;
  93} VhostUserLog;
  94
  95typedef struct VhostUserMsg {
  96    VhostUserRequest request;
  97
  98#define VHOST_USER_VERSION_MASK     (0x3)
  99#define VHOST_USER_REPLY_MASK       (0x1<<2)
 100    uint32_t flags;
 101    uint32_t size; /* the following payload size */
 102    union {
 103#define VHOST_USER_VRING_IDX_MASK   (0xff)
 104#define VHOST_USER_VRING_NOFD_MASK  (0x1<<8)
 105        uint64_t u64;
 106        struct vhost_vring_state state;
 107        struct vhost_vring_addr addr;
 108        VhostUserMemory memory;
 109        VhostUserLog log;
 110    } payload;
 111} QEMU_PACKED VhostUserMsg;
 112
 113static VhostUserMsg m __attribute__ ((unused));
 114#define VHOST_USER_HDR_SIZE (sizeof(m.request) \
 115                            + sizeof(m.flags) \
 116                            + sizeof(m.size))
 117
 118#define VHOST_USER_PAYLOAD_SIZE (sizeof(m) - VHOST_USER_HDR_SIZE)
 119
 120/* The version of the protocol we support */
 121#define VHOST_USER_VERSION    (0x1)
 122/*****************************************************************************/
 123
 124typedef struct TestServer {
 125    gchar *socket_path;
 126    gchar *mig_path;
 127    gchar *chr_name;
 128    CharDriverState *chr;
 129    int fds_num;
 130    int fds[VHOST_MEMORY_MAX_NREGIONS];
 131    VhostUserMemory memory;
 132    GMutex data_mutex;
 133    GCond data_cond;
 134    int log_fd;
 135    uint64_t rings;
 136} TestServer;
 137
 138#if !GLIB_CHECK_VERSION(2, 32, 0)
 139static gboolean g_cond_wait_until(CompatGCond cond, CompatGMutex mutex,
 140                                  gint64 end_time)
 141{
 142    gboolean ret = FALSE;
 143    end_time -= g_get_monotonic_time();
 144    GTimeVal time = { end_time / G_TIME_SPAN_SECOND,
 145                      end_time % G_TIME_SPAN_SECOND };
 146    ret = g_cond_timed_wait(cond, mutex, &time);
 147    return ret;
 148}
 149#endif
 150
 151static const char *tmpfs;
 152static const char *root;
 153
 154static void wait_for_fds(TestServer *s)
 155{
 156    gint64 end_time;
 157
 158    g_mutex_lock(&s->data_mutex);
 159
 160    end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
 161    while (!s->fds_num) {
 162        if (!g_cond_wait_until(&s->data_cond, &s->data_mutex, end_time)) {
 163            /* timeout has passed */
 164            g_assert(s->fds_num);
 165            break;
 166        }
 167    }
 168
 169    /* check for sanity */
 170    g_assert_cmpint(s->fds_num, >, 0);
 171    g_assert_cmpint(s->fds_num, ==, s->memory.nregions);
 172
 173    g_mutex_unlock(&s->data_mutex);
 174}
 175
 176static void read_guest_mem(const void *data)
 177{
 178    TestServer *s = (void *)data;
 179    uint32_t *guest_mem;
 180    int i, j;
 181    size_t size;
 182
 183    wait_for_fds(s);
 184
 185    g_mutex_lock(&s->data_mutex);
 186
 187    /* iterate all regions */
 188    for (i = 0; i < s->fds_num; i++) {
 189
 190        /* We'll check only the region statring at 0x0*/
 191        if (s->memory.regions[i].guest_phys_addr != 0x0) {
 192            continue;
 193        }
 194
 195        g_assert_cmpint(s->memory.regions[i].memory_size, >, 1024);
 196
 197        size = s->memory.regions[i].memory_size +
 198            s->memory.regions[i].mmap_offset;
 199
 200        guest_mem = mmap(0, size, PROT_READ | PROT_WRITE,
 201                         MAP_SHARED, s->fds[i], 0);
 202
 203        g_assert(guest_mem != MAP_FAILED);
 204        guest_mem += (s->memory.regions[i].mmap_offset / sizeof(*guest_mem));
 205
 206        for (j = 0; j < 256; j++) {
 207            uint32_t a = readl(s->memory.regions[i].guest_phys_addr + j*4);
 208            uint32_t b = guest_mem[j];
 209
 210            g_assert_cmpint(a, ==, b);
 211        }
 212
 213        munmap(guest_mem, s->memory.regions[i].memory_size);
 214    }
 215
 216    g_mutex_unlock(&s->data_mutex);
 217}
 218
 219static void *thread_function(void *data)
 220{
 221    GMainLoop *loop = data;
 222    g_main_loop_run(loop);
 223    return NULL;
 224}
 225
 226static int chr_can_read(void *opaque)
 227{
 228    return VHOST_USER_HDR_SIZE;
 229}
 230
 231static void chr_read(void *opaque, const uint8_t *buf, int size)
 232{
 233    TestServer *s = opaque;
 234    CharDriverState *chr = s->chr;
 235    VhostUserMsg msg;
 236    uint8_t *p = (uint8_t *) &msg;
 237    int fd;
 238
 239    if (size != VHOST_USER_HDR_SIZE) {
 240        g_test_message("Wrong message size received %d\n", size);
 241        return;
 242    }
 243
 244    g_mutex_lock(&s->data_mutex);
 245    memcpy(p, buf, VHOST_USER_HDR_SIZE);
 246
 247    if (msg.size) {
 248        p += VHOST_USER_HDR_SIZE;
 249        g_assert_cmpint(qemu_chr_fe_read_all(chr, p, msg.size), ==, msg.size);
 250    }
 251
 252    switch (msg.request) {
 253    case VHOST_USER_GET_FEATURES:
 254        /* send back features to qemu */
 255        msg.flags |= VHOST_USER_REPLY_MASK;
 256        msg.size = sizeof(m.payload.u64);
 257        msg.payload.u64 = 0x1ULL << VHOST_F_LOG_ALL |
 258            0x1ULL << VHOST_USER_F_PROTOCOL_FEATURES;
 259        p = (uint8_t *) &msg;
 260        qemu_chr_fe_write_all(chr, p, VHOST_USER_HDR_SIZE + msg.size);
 261        break;
 262
 263    case VHOST_USER_SET_FEATURES:
 264        g_assert_cmpint(msg.payload.u64 & (0x1ULL << VHOST_USER_F_PROTOCOL_FEATURES),
 265                        !=, 0ULL);
 266        break;
 267
 268    case VHOST_USER_GET_PROTOCOL_FEATURES:
 269        /* send back features to qemu */
 270        msg.flags |= VHOST_USER_REPLY_MASK;
 271        msg.size = sizeof(m.payload.u64);
 272        msg.payload.u64 = 1 << VHOST_USER_PROTOCOL_F_LOG_SHMFD;
 273        p = (uint8_t *) &msg;
 274        qemu_chr_fe_write_all(chr, p, VHOST_USER_HDR_SIZE + msg.size);
 275        break;
 276
 277    case VHOST_USER_GET_VRING_BASE:
 278        /* send back vring base to qemu */
 279        msg.flags |= VHOST_USER_REPLY_MASK;
 280        msg.size = sizeof(m.payload.state);
 281        msg.payload.state.num = 0;
 282        p = (uint8_t *) &msg;
 283        qemu_chr_fe_write_all(chr, p, VHOST_USER_HDR_SIZE + msg.size);
 284
 285        assert(msg.payload.state.index < 2);
 286        s->rings &= ~(0x1ULL << msg.payload.state.index);
 287        break;
 288
 289    case VHOST_USER_SET_MEM_TABLE:
 290        /* received the mem table */
 291        memcpy(&s->memory, &msg.payload.memory, sizeof(msg.payload.memory));
 292        s->fds_num = qemu_chr_fe_get_msgfds(chr, s->fds, G_N_ELEMENTS(s->fds));
 293
 294        /* signal the test that it can continue */
 295        g_cond_signal(&s->data_cond);
 296        break;
 297
 298    case VHOST_USER_SET_VRING_KICK:
 299    case VHOST_USER_SET_VRING_CALL:
 300        /* consume the fd */
 301        qemu_chr_fe_get_msgfds(chr, &fd, 1);
 302        /*
 303         * This is a non-blocking eventfd.
 304         * The receive function forces it to be blocking,
 305         * so revert it back to non-blocking.
 306         */
 307        qemu_set_nonblock(fd);
 308        break;
 309
 310    case VHOST_USER_SET_LOG_BASE:
 311        if (s->log_fd != -1) {
 312            close(s->log_fd);
 313            s->log_fd = -1;
 314        }
 315        qemu_chr_fe_get_msgfds(chr, &s->log_fd, 1);
 316        msg.flags |= VHOST_USER_REPLY_MASK;
 317        msg.size = 0;
 318        p = (uint8_t *) &msg;
 319        qemu_chr_fe_write_all(chr, p, VHOST_USER_HDR_SIZE);
 320
 321        g_cond_signal(&s->data_cond);
 322        break;
 323
 324    case VHOST_USER_SET_VRING_BASE:
 325        assert(msg.payload.state.index < 2);
 326        s->rings |= 0x1ULL << msg.payload.state.index;
 327        break;
 328
 329    default:
 330        break;
 331    }
 332
 333    g_mutex_unlock(&s->data_mutex);
 334}
 335
 336static const char *init_hugepagefs(const char *path)
 337{
 338    struct statfs fs;
 339    int ret;
 340
 341    if (access(path, R_OK | W_OK | X_OK)) {
 342        g_test_message("access on path (%s): %s\n", path, strerror(errno));
 343        return NULL;
 344    }
 345
 346    do {
 347        ret = statfs(path, &fs);
 348    } while (ret != 0 && errno == EINTR);
 349
 350    if (ret != 0) {
 351        g_test_message("statfs on path (%s): %s\n", path, strerror(errno));
 352        return NULL;
 353    }
 354
 355    if (fs.f_type != HUGETLBFS_MAGIC) {
 356        g_test_message("Warning: path not on HugeTLBFS: %s\n", path);
 357        return NULL;
 358    }
 359
 360    return path;
 361}
 362
 363static TestServer *test_server_new(const gchar *name)
 364{
 365    TestServer *server = g_new0(TestServer, 1);
 366    gchar *chr_path;
 367
 368    server->socket_path = g_strdup_printf("%s/%s.sock", tmpfs, name);
 369    server->mig_path = g_strdup_printf("%s/%s.mig", tmpfs, name);
 370
 371    chr_path = g_strdup_printf("unix:%s,server,nowait", server->socket_path);
 372    server->chr_name = g_strdup_printf("chr-%s", name);
 373    server->chr = qemu_chr_new(server->chr_name, chr_path, NULL);
 374    g_free(chr_path);
 375
 376    qemu_chr_add_handlers(server->chr, chr_can_read, chr_read, NULL, server);
 377
 378    g_mutex_init(&server->data_mutex);
 379    g_cond_init(&server->data_cond);
 380
 381    server->log_fd = -1;
 382
 383    return server;
 384}
 385
 386#define GET_QEMU_CMD(s)                                                        \
 387    g_strdup_printf(QEMU_CMD, 512, 512, (root), (s)->chr_name,                 \
 388                    (s)->socket_path, (s)->chr_name)
 389
 390#define GET_QEMU_CMDE(s, mem, extra, ...)                                      \
 391    g_strdup_printf(QEMU_CMD extra, (mem), (mem), (root), (s)->chr_name,       \
 392                    (s)->socket_path, (s)->chr_name, ##__VA_ARGS__)
 393
 394static gboolean _test_server_free(TestServer *server)
 395{
 396    int i;
 397
 398    qemu_chr_delete(server->chr);
 399
 400    for (i = 0; i < server->fds_num; i++) {
 401        close(server->fds[i]);
 402    }
 403
 404    if (server->log_fd != -1) {
 405        close(server->log_fd);
 406    }
 407
 408    unlink(server->socket_path);
 409    g_free(server->socket_path);
 410
 411    unlink(server->mig_path);
 412    g_free(server->mig_path);
 413
 414    g_free(server->chr_name);
 415    g_free(server);
 416
 417    return FALSE;
 418}
 419
 420static void test_server_free(TestServer *server)
 421{
 422    g_idle_add((GSourceFunc)_test_server_free, server);
 423}
 424
 425static void wait_for_log_fd(TestServer *s)
 426{
 427    gint64 end_time;
 428
 429    g_mutex_lock(&s->data_mutex);
 430    end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
 431    while (s->log_fd == -1) {
 432        if (!g_cond_wait_until(&s->data_cond, &s->data_mutex, end_time)) {
 433            /* timeout has passed */
 434            g_assert(s->log_fd != -1);
 435            break;
 436        }
 437    }
 438
 439    g_mutex_unlock(&s->data_mutex);
 440}
 441
 442static void write_guest_mem(TestServer *s, uint32_t seed)
 443{
 444    uint32_t *guest_mem;
 445    int i, j;
 446    size_t size;
 447
 448    wait_for_fds(s);
 449
 450    /* iterate all regions */
 451    for (i = 0; i < s->fds_num; i++) {
 452
 453        /* We'll write only the region statring at 0x0 */
 454        if (s->memory.regions[i].guest_phys_addr != 0x0) {
 455            continue;
 456        }
 457
 458        g_assert_cmpint(s->memory.regions[i].memory_size, >, 1024);
 459
 460        size = s->memory.regions[i].memory_size +
 461            s->memory.regions[i].mmap_offset;
 462
 463        guest_mem = mmap(0, size, PROT_READ | PROT_WRITE,
 464                         MAP_SHARED, s->fds[i], 0);
 465
 466        g_assert(guest_mem != MAP_FAILED);
 467        guest_mem += (s->memory.regions[i].mmap_offset / sizeof(*guest_mem));
 468
 469        for (j = 0; j < 256; j++) {
 470            guest_mem[j] = seed + j;
 471        }
 472
 473        munmap(guest_mem, s->memory.regions[i].memory_size);
 474        break;
 475    }
 476}
 477
 478static guint64 get_log_size(TestServer *s)
 479{
 480    guint64 log_size = 0;
 481    int i;
 482
 483    for (i = 0; i < s->memory.nregions; ++i) {
 484        VhostUserMemoryRegion *reg = &s->memory.regions[i];
 485        guint64 last = range_get_last(reg->guest_phys_addr,
 486                                       reg->memory_size);
 487        log_size = MAX(log_size, last / (8 * VHOST_LOG_PAGE) + 1);
 488    }
 489
 490    return log_size;
 491}
 492
 493typedef struct TestMigrateSource {
 494    GSource source;
 495    TestServer *src;
 496    TestServer *dest;
 497} TestMigrateSource;
 498
 499static gboolean
 500test_migrate_source_check(GSource *source)
 501{
 502    TestMigrateSource *t = (TestMigrateSource *)source;
 503    gboolean overlap = t->src->rings && t->dest->rings;
 504
 505    g_assert(!overlap);
 506
 507    return FALSE;
 508}
 509
 510#if !GLIB_CHECK_VERSION(2,36,0)
 511/* this callback is unnecessary with glib >2.36, the default
 512 * prepare for the source does the same */
 513static gboolean
 514test_migrate_source_prepare(GSource *source, gint *timeout)
 515{
 516    *timeout = -1;
 517    return FALSE;
 518}
 519#endif
 520
 521GSourceFuncs test_migrate_source_funcs = {
 522#if !GLIB_CHECK_VERSION(2,36,0)
 523    .prepare = test_migrate_source_prepare,
 524#endif
 525    .check = test_migrate_source_check,
 526};
 527
 528static void test_migrate(void)
 529{
 530    TestServer *s = test_server_new("src");
 531    TestServer *dest = test_server_new("dest");
 532    char *uri = g_strdup_printf("%s%s", "unix:", dest->mig_path);
 533    QTestState *global = global_qtest, *from, *to;
 534    GSource *source;
 535    gchar *cmd;
 536    QDict *rsp;
 537    guint8 *log;
 538    guint64 size;
 539
 540    cmd = GET_QEMU_CMDE(s, 2, "");
 541    from = qtest_start(cmd);
 542    g_free(cmd);
 543
 544    wait_for_fds(s);
 545    size = get_log_size(s);
 546    g_assert_cmpint(size, ==, (2 * 1024 * 1024) / (VHOST_LOG_PAGE * 8));
 547
 548    cmd = GET_QEMU_CMDE(dest, 2, " -incoming %s", uri);
 549    to = qtest_init(cmd);
 550    g_free(cmd);
 551
 552    source = g_source_new(&test_migrate_source_funcs,
 553                          sizeof(TestMigrateSource));
 554    ((TestMigrateSource *)source)->src = s;
 555    ((TestMigrateSource *)source)->dest = dest;
 556    g_source_attach(source, NULL);
 557
 558    /* slow down migration to have time to fiddle with log */
 559    /* TODO: qtest could learn to break on some places */
 560    rsp = qmp("{ 'execute': 'migrate_set_speed',"
 561              "'arguments': { 'value': 10 } }");
 562    g_assert(qdict_haskey(rsp, "return"));
 563    QDECREF(rsp);
 564
 565    cmd = g_strdup_printf("{ 'execute': 'migrate',"
 566                          "'arguments': { 'uri': '%s' } }",
 567                          uri);
 568    rsp = qmp(cmd);
 569    g_free(cmd);
 570    g_assert(qdict_haskey(rsp, "return"));
 571    QDECREF(rsp);
 572
 573    wait_for_log_fd(s);
 574
 575    log = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, s->log_fd, 0);
 576    g_assert(log != MAP_FAILED);
 577
 578    /* modify first page */
 579    write_guest_mem(s, 0x42);
 580    log[0] = 1;
 581    munmap(log, size);
 582
 583    /* speed things up */
 584    rsp = qmp("{ 'execute': 'migrate_set_speed',"
 585              "'arguments': { 'value': 0 } }");
 586    g_assert(qdict_haskey(rsp, "return"));
 587    QDECREF(rsp);
 588
 589    qmp_eventwait("STOP");
 590
 591    global_qtest = to;
 592    qmp_eventwait("RESUME");
 593
 594    read_guest_mem(dest);
 595
 596    g_source_destroy(source);
 597    g_source_unref(source);
 598
 599    qtest_quit(to);
 600    test_server_free(dest);
 601    qtest_quit(from);
 602    test_server_free(s);
 603    g_free(uri);
 604
 605    global_qtest = global;
 606}
 607
 608int main(int argc, char **argv)
 609{
 610    QTestState *s = NULL;
 611    TestServer *server = NULL;
 612    const char *hugefs;
 613    char *qemu_cmd = NULL;
 614    int ret;
 615    char template[] = "/tmp/vhost-test-XXXXXX";
 616    GMainLoop *loop;
 617    GThread *thread;
 618
 619    g_test_init(&argc, &argv, NULL);
 620
 621    module_call_init(MODULE_INIT_QOM);
 622    qemu_add_opts(&qemu_chardev_opts);
 623
 624    tmpfs = mkdtemp(template);
 625    if (!tmpfs) {
 626        g_test_message("mkdtemp on path (%s): %s\n", template, strerror(errno));
 627    }
 628    g_assert(tmpfs);
 629
 630    hugefs = getenv("QTEST_HUGETLBFS_PATH");
 631    if (hugefs) {
 632        root = init_hugepagefs(hugefs);
 633        g_assert(root);
 634    } else {
 635        root = tmpfs;
 636    }
 637
 638    server = test_server_new("test");
 639
 640    loop = g_main_loop_new(NULL, FALSE);
 641    /* run the main loop thread so the chardev may operate */
 642    thread = g_thread_new(NULL, thread_function, loop);
 643
 644    qemu_cmd = GET_QEMU_CMD(server);
 645
 646    s = qtest_start(qemu_cmd);
 647    g_free(qemu_cmd);
 648
 649    qtest_add_data_func("/vhost-user/read-guest-mem", server, read_guest_mem);
 650    qtest_add_func("/vhost-user/migrate", test_migrate);
 651
 652    ret = g_test_run();
 653
 654    if (s) {
 655        qtest_quit(s);
 656    }
 657
 658    /* cleanup */
 659    test_server_free(server);
 660
 661    /* finish the helper thread and dispatch pending sources */
 662    g_main_loop_quit(loop);
 663    g_thread_join(thread);
 664    while (g_main_context_pending(NULL)) {
 665        g_main_context_iteration (NULL, TRUE);
 666    }
 667    g_main_loop_unref(loop);
 668
 669    ret = rmdir(tmpfs);
 670    if (ret != 0) {
 671        g_test_message("unable to rmdir: path (%s): %s\n",
 672                       tmpfs, strerror(errno));
 673    }
 674    g_assert_cmpint(ret, ==, 0);
 675
 676    return ret;
 677}
 678