qemu/block/nfs.c
<<
>>
Prefs
   1/*
   2 * QEMU Block driver for native access to files on NFS shares
   3 *
   4 * Copyright (c) 2014-2016 Peter Lieven <pl@kamp.de>
   5 *
   6 * Permission is hereby granted, free of charge, to any person obtaining a copy
   7 * of this software and associated documentation files (the "Software"), to deal
   8 * in the Software without restriction, including without limitation the rights
   9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10 * copies of the Software, and to permit persons to whom the Software is
  11 * furnished to do so, subject to the following conditions:
  12 *
  13 * The above copyright notice and this permission notice shall be included in
  14 * all copies or substantial portions of the Software.
  15 *
  16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22 * THE SOFTWARE.
  23 */
  24
  25#include "qemu/osdep.h"
  26
  27#include <poll.h>
  28#include "qemu-common.h"
  29#include "qemu/config-file.h"
  30#include "qemu/error-report.h"
  31#include "qapi/error.h"
  32#include "block/block_int.h"
  33#include "trace.h"
  34#include "qemu/iov.h"
  35#include "qemu/uri.h"
  36#include "qemu/cutils.h"
  37#include "sysemu/sysemu.h"
  38#include "qapi/qmp/qdict.h"
  39#include "qapi/qmp/qint.h"
  40#include "qapi/qmp/qstring.h"
  41#include "qapi-visit.h"
  42#include "qapi/qobject-input-visitor.h"
  43#include "qapi/qobject-output-visitor.h"
  44#include <nfsc/libnfs.h>
  45
  46
  47#define QEMU_NFS_MAX_READAHEAD_SIZE 1048576
  48#define QEMU_NFS_MAX_PAGECACHE_SIZE (8388608 / NFS_BLKSIZE)
  49#define QEMU_NFS_MAX_DEBUG_LEVEL 2
  50
  51typedef struct NFSClient {
  52    struct nfs_context *context;
  53    struct nfsfh *fh;
  54    int events;
  55    bool has_zero_init;
  56    AioContext *aio_context;
  57    QemuMutex mutex;
  58    blkcnt_t st_blocks;
  59    bool cache_used;
  60    NFSServer *server;
  61    char *path;
  62    int64_t uid, gid, tcp_syncnt, readahead, pagecache, debug;
  63} NFSClient;
  64
  65typedef struct NFSRPC {
  66    BlockDriverState *bs;
  67    int ret;
  68    int complete;
  69    QEMUIOVector *iov;
  70    struct stat *st;
  71    Coroutine *co;
  72    NFSClient *client;
  73} NFSRPC;
  74
  75static int nfs_parse_uri(const char *filename, QDict *options, Error **errp)
  76{
  77    URI *uri = NULL;
  78    QueryParams *qp = NULL;
  79    int ret = -EINVAL, i;
  80
  81    uri = uri_parse(filename);
  82    if (!uri) {
  83        error_setg(errp, "Invalid URI specified");
  84        goto out;
  85    }
  86    if (g_strcmp0(uri->scheme, "nfs") != 0) {
  87        error_setg(errp, "URI scheme must be 'nfs'");
  88        goto out;
  89    }
  90
  91    if (!uri->server) {
  92        error_setg(errp, "missing hostname in URI");
  93        goto out;
  94    }
  95
  96    if (!uri->path) {
  97        error_setg(errp, "missing file path in URI");
  98        goto out;
  99    }
 100
 101    qp = query_params_parse(uri->query);
 102    if (!qp) {
 103        error_setg(errp, "could not parse query parameters");
 104        goto out;
 105    }
 106
 107    qdict_put_str(options, "server.host", uri->server);
 108    qdict_put_str(options, "server.type", "inet");
 109    qdict_put_str(options, "path", uri->path);
 110
 111    for (i = 0; i < qp->n; i++) {
 112        unsigned long long val;
 113        if (!qp->p[i].value) {
 114            error_setg(errp, "Value for NFS parameter expected: %s",
 115                       qp->p[i].name);
 116            goto out;
 117        }
 118        if (parse_uint_full(qp->p[i].value, &val, 0)) {
 119            error_setg(errp, "Illegal value for NFS parameter: %s",
 120                       qp->p[i].name);
 121            goto out;
 122        }
 123        if (!strcmp(qp->p[i].name, "uid")) {
 124            qdict_put_str(options, "user", qp->p[i].value);
 125        } else if (!strcmp(qp->p[i].name, "gid")) {
 126            qdict_put_str(options, "group", qp->p[i].value);
 127        } else if (!strcmp(qp->p[i].name, "tcp-syncnt")) {
 128            qdict_put_str(options, "tcp-syn-count", qp->p[i].value);
 129        } else if (!strcmp(qp->p[i].name, "readahead")) {
 130            qdict_put_str(options, "readahead-size", qp->p[i].value);
 131        } else if (!strcmp(qp->p[i].name, "pagecache")) {
 132            qdict_put_str(options, "page-cache-size", qp->p[i].value);
 133        } else if (!strcmp(qp->p[i].name, "debug")) {
 134            qdict_put_str(options, "debug", qp->p[i].value);
 135        } else {
 136            error_setg(errp, "Unknown NFS parameter name: %s",
 137                       qp->p[i].name);
 138            goto out;
 139        }
 140    }
 141    ret = 0;
 142out:
 143    if (qp) {
 144        query_params_free(qp);
 145    }
 146    if (uri) {
 147        uri_free(uri);
 148    }
 149    return ret;
 150}
 151
 152static bool nfs_has_filename_options_conflict(QDict *options, Error **errp)
 153{
 154    const QDictEntry *qe;
 155
 156    for (qe = qdict_first(options); qe; qe = qdict_next(options, qe)) {
 157        if (!strcmp(qe->key, "host") ||
 158            !strcmp(qe->key, "path") ||
 159            !strcmp(qe->key, "user") ||
 160            !strcmp(qe->key, "group") ||
 161            !strcmp(qe->key, "tcp-syn-count") ||
 162            !strcmp(qe->key, "readahead-size") ||
 163            !strcmp(qe->key, "page-cache-size") ||
 164            !strcmp(qe->key, "debug") ||
 165            strstart(qe->key, "server.", NULL))
 166        {
 167            error_setg(errp, "Option %s cannot be used with a filename",
 168                       qe->key);
 169            return true;
 170        }
 171    }
 172
 173    return false;
 174}
 175
 176static void nfs_parse_filename(const char *filename, QDict *options,
 177                               Error **errp)
 178{
 179    if (nfs_has_filename_options_conflict(options, errp)) {
 180        return;
 181    }
 182
 183    nfs_parse_uri(filename, options, errp);
 184}
 185
 186static void nfs_process_read(void *arg);
 187static void nfs_process_write(void *arg);
 188
 189/* Called with QemuMutex held.  */
 190static void nfs_set_events(NFSClient *client)
 191{
 192    int ev = nfs_which_events(client->context);
 193    if (ev != client->events) {
 194        aio_set_fd_handler(client->aio_context, nfs_get_fd(client->context),
 195                           false,
 196                           (ev & POLLIN) ? nfs_process_read : NULL,
 197                           (ev & POLLOUT) ? nfs_process_write : NULL,
 198                           NULL, client);
 199
 200    }
 201    client->events = ev;
 202}
 203
 204static void nfs_process_read(void *arg)
 205{
 206    NFSClient *client = arg;
 207
 208    qemu_mutex_lock(&client->mutex);
 209    nfs_service(client->context, POLLIN);
 210    nfs_set_events(client);
 211    qemu_mutex_unlock(&client->mutex);
 212}
 213
 214static void nfs_process_write(void *arg)
 215{
 216    NFSClient *client = arg;
 217
 218    qemu_mutex_lock(&client->mutex);
 219    nfs_service(client->context, POLLOUT);
 220    nfs_set_events(client);
 221    qemu_mutex_unlock(&client->mutex);
 222}
 223
 224static void nfs_co_init_task(BlockDriverState *bs, NFSRPC *task)
 225{
 226    *task = (NFSRPC) {
 227        .co             = qemu_coroutine_self(),
 228        .bs             = bs,
 229        .client         = bs->opaque,
 230    };
 231}
 232
 233static void nfs_co_generic_bh_cb(void *opaque)
 234{
 235    NFSRPC *task = opaque;
 236
 237    task->complete = 1;
 238    aio_co_wake(task->co);
 239}
 240
 241/* Called (via nfs_service) with QemuMutex held.  */
 242static void
 243nfs_co_generic_cb(int ret, struct nfs_context *nfs, void *data,
 244                  void *private_data)
 245{
 246    NFSRPC *task = private_data;
 247    task->ret = ret;
 248    assert(!task->st);
 249    if (task->ret > 0 && task->iov) {
 250        if (task->ret <= task->iov->size) {
 251            qemu_iovec_from_buf(task->iov, 0, data, task->ret);
 252        } else {
 253            task->ret = -EIO;
 254        }
 255    }
 256    if (task->ret < 0) {
 257        error_report("NFS Error: %s", nfs_get_error(nfs));
 258    }
 259    aio_bh_schedule_oneshot(task->client->aio_context,
 260                            nfs_co_generic_bh_cb, task);
 261}
 262
 263static int coroutine_fn nfs_co_preadv(BlockDriverState *bs, uint64_t offset,
 264                                      uint64_t bytes, QEMUIOVector *iov,
 265                                      int flags)
 266{
 267    NFSClient *client = bs->opaque;
 268    NFSRPC task;
 269
 270    nfs_co_init_task(bs, &task);
 271    task.iov = iov;
 272
 273    qemu_mutex_lock(&client->mutex);
 274    if (nfs_pread_async(client->context, client->fh,
 275                        offset, bytes, nfs_co_generic_cb, &task) != 0) {
 276        qemu_mutex_unlock(&client->mutex);
 277        return -ENOMEM;
 278    }
 279
 280    nfs_set_events(client);
 281    qemu_mutex_unlock(&client->mutex);
 282    while (!task.complete) {
 283        qemu_coroutine_yield();
 284    }
 285
 286    if (task.ret < 0) {
 287        return task.ret;
 288    }
 289
 290    /* zero pad short reads */
 291    if (task.ret < iov->size) {
 292        qemu_iovec_memset(iov, task.ret, 0, iov->size - task.ret);
 293    }
 294
 295    return 0;
 296}
 297
 298static int coroutine_fn nfs_co_pwritev(BlockDriverState *bs, uint64_t offset,
 299                                       uint64_t bytes, QEMUIOVector *iov,
 300                                       int flags)
 301{
 302    NFSClient *client = bs->opaque;
 303    NFSRPC task;
 304    char *buf = NULL;
 305    bool my_buffer = false;
 306
 307    nfs_co_init_task(bs, &task);
 308
 309    if (iov->niov != 1) {
 310        buf = g_try_malloc(bytes);
 311        if (bytes && buf == NULL) {
 312            return -ENOMEM;
 313        }
 314        qemu_iovec_to_buf(iov, 0, buf, bytes);
 315        my_buffer = true;
 316    } else {
 317        buf = iov->iov[0].iov_base;
 318    }
 319
 320    qemu_mutex_lock(&client->mutex);
 321    if (nfs_pwrite_async(client->context, client->fh,
 322                         offset, bytes, buf,
 323                         nfs_co_generic_cb, &task) != 0) {
 324        qemu_mutex_unlock(&client->mutex);
 325        if (my_buffer) {
 326            g_free(buf);
 327        }
 328        return -ENOMEM;
 329    }
 330
 331    nfs_set_events(client);
 332    qemu_mutex_unlock(&client->mutex);
 333    while (!task.complete) {
 334        qemu_coroutine_yield();
 335    }
 336
 337    if (my_buffer) {
 338        g_free(buf);
 339    }
 340
 341    if (task.ret != bytes) {
 342        return task.ret < 0 ? task.ret : -EIO;
 343    }
 344
 345    return 0;
 346}
 347
 348static int coroutine_fn nfs_co_flush(BlockDriverState *bs)
 349{
 350    NFSClient *client = bs->opaque;
 351    NFSRPC task;
 352
 353    nfs_co_init_task(bs, &task);
 354
 355    qemu_mutex_lock(&client->mutex);
 356    if (nfs_fsync_async(client->context, client->fh, nfs_co_generic_cb,
 357                        &task) != 0) {
 358        qemu_mutex_unlock(&client->mutex);
 359        return -ENOMEM;
 360    }
 361
 362    nfs_set_events(client);
 363    qemu_mutex_unlock(&client->mutex);
 364    while (!task.complete) {
 365        qemu_coroutine_yield();
 366    }
 367
 368    return task.ret;
 369}
 370
 371static QemuOptsList runtime_opts = {
 372    .name = "nfs",
 373    .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
 374    .desc = {
 375        {
 376            .name = "path",
 377            .type = QEMU_OPT_STRING,
 378            .help = "Path of the image on the host",
 379        },
 380        {
 381            .name = "user",
 382            .type = QEMU_OPT_NUMBER,
 383            .help = "UID value to use when talking to the server",
 384        },
 385        {
 386            .name = "group",
 387            .type = QEMU_OPT_NUMBER,
 388            .help = "GID value to use when talking to the server",
 389        },
 390        {
 391            .name = "tcp-syn-count",
 392            .type = QEMU_OPT_NUMBER,
 393            .help = "Number of SYNs to send during the session establish",
 394        },
 395        {
 396            .name = "readahead-size",
 397            .type = QEMU_OPT_NUMBER,
 398            .help = "Set the readahead size in bytes",
 399        },
 400        {
 401            .name = "page-cache-size",
 402            .type = QEMU_OPT_NUMBER,
 403            .help = "Set the pagecache size in bytes",
 404        },
 405        {
 406            .name = "debug",
 407            .type = QEMU_OPT_NUMBER,
 408            .help = "Set the NFS debug level (max 2)",
 409        },
 410        { /* end of list */ }
 411    },
 412};
 413
 414static void nfs_detach_aio_context(BlockDriverState *bs)
 415{
 416    NFSClient *client = bs->opaque;
 417
 418    aio_set_fd_handler(client->aio_context, nfs_get_fd(client->context),
 419                       false, NULL, NULL, NULL, NULL);
 420    client->events = 0;
 421}
 422
 423static void nfs_attach_aio_context(BlockDriverState *bs,
 424                                   AioContext *new_context)
 425{
 426    NFSClient *client = bs->opaque;
 427
 428    client->aio_context = new_context;
 429    nfs_set_events(client);
 430}
 431
 432static void nfs_client_close(NFSClient *client)
 433{
 434    if (client->context) {
 435        if (client->fh) {
 436            nfs_close(client->context, client->fh);
 437            client->fh = NULL;
 438        }
 439        aio_set_fd_handler(client->aio_context, nfs_get_fd(client->context),
 440                           false, NULL, NULL, NULL, NULL);
 441        nfs_destroy_context(client->context);
 442        client->context = NULL;
 443    }
 444    g_free(client->path);
 445    qemu_mutex_destroy(&client->mutex);
 446    qapi_free_NFSServer(client->server);
 447    client->server = NULL;
 448}
 449
 450static void nfs_file_close(BlockDriverState *bs)
 451{
 452    NFSClient *client = bs->opaque;
 453    nfs_client_close(client);
 454}
 455
 456static NFSServer *nfs_config(QDict *options, Error **errp)
 457{
 458    NFSServer *server = NULL;
 459    QDict *addr = NULL;
 460    QObject *crumpled_addr = NULL;
 461    Visitor *iv = NULL;
 462    Error *local_error = NULL;
 463
 464    qdict_extract_subqdict(options, &addr, "server.");
 465    if (!qdict_size(addr)) {
 466        error_setg(errp, "NFS server address missing");
 467        goto out;
 468    }
 469
 470    crumpled_addr = qdict_crumple(addr, errp);
 471    if (!crumpled_addr) {
 472        goto out;
 473    }
 474
 475    /*
 476     * Caution: this works only because all scalar members of
 477     * NFSServer are QString in @crumpled_addr.  The visitor expects
 478     * @crumpled_addr to be typed according to the QAPI schema.  It
 479     * is when @options come from -blockdev or blockdev_add.  But when
 480     * they come from -drive, they're all QString.
 481     */
 482    iv = qobject_input_visitor_new(crumpled_addr);
 483    visit_type_NFSServer(iv, NULL, &server, &local_error);
 484    if (local_error) {
 485        error_propagate(errp, local_error);
 486        goto out;
 487    }
 488
 489out:
 490    QDECREF(addr);
 491    qobject_decref(crumpled_addr);
 492    visit_free(iv);
 493    return server;
 494}
 495
 496
 497static int64_t nfs_client_open(NFSClient *client, QDict *options,
 498                               int flags, Error **errp, int open_flags)
 499{
 500    int ret = -EINVAL;
 501    QemuOpts *opts = NULL;
 502    Error *local_err = NULL;
 503    struct stat st;
 504    char *file = NULL, *strp = NULL;
 505
 506    qemu_mutex_init(&client->mutex);
 507    opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
 508    qemu_opts_absorb_qdict(opts, options, &local_err);
 509    if (local_err) {
 510        error_propagate(errp, local_err);
 511        ret = -EINVAL;
 512        goto fail;
 513    }
 514
 515    client->path = g_strdup(qemu_opt_get(opts, "path"));
 516    if (!client->path) {
 517        ret = -EINVAL;
 518        error_setg(errp, "No path was specified");
 519        goto fail;
 520    }
 521
 522    strp = strrchr(client->path, '/');
 523    if (strp == NULL) {
 524        error_setg(errp, "Invalid URL specified");
 525        goto fail;
 526    }
 527    file = g_strdup(strp);
 528    *strp = 0;
 529
 530    /* Pop the config into our state object, Exit if invalid */
 531    client->server = nfs_config(options, errp);
 532    if (!client->server) {
 533        ret = -EINVAL;
 534        goto fail;
 535    }
 536
 537    client->context = nfs_init_context();
 538    if (client->context == NULL) {
 539        error_setg(errp, "Failed to init NFS context");
 540        goto fail;
 541    }
 542
 543    if (qemu_opt_get(opts, "user")) {
 544        client->uid = qemu_opt_get_number(opts, "user", 0);
 545        nfs_set_uid(client->context, client->uid);
 546    }
 547
 548    if (qemu_opt_get(opts, "group")) {
 549        client->gid = qemu_opt_get_number(opts, "group", 0);
 550        nfs_set_gid(client->context, client->gid);
 551    }
 552
 553    if (qemu_opt_get(opts, "tcp-syn-count")) {
 554        client->tcp_syncnt = qemu_opt_get_number(opts, "tcp-syn-count", 0);
 555        nfs_set_tcp_syncnt(client->context, client->tcp_syncnt);
 556    }
 557
 558#ifdef LIBNFS_FEATURE_READAHEAD
 559    if (qemu_opt_get(opts, "readahead-size")) {
 560        if (open_flags & BDRV_O_NOCACHE) {
 561            error_setg(errp, "Cannot enable NFS readahead "
 562                             "if cache.direct = on");
 563            goto fail;
 564        }
 565        client->readahead = qemu_opt_get_number(opts, "readahead-size", 0);
 566        if (client->readahead > QEMU_NFS_MAX_READAHEAD_SIZE) {
 567            error_report("NFS Warning: Truncating NFS readahead "
 568                         "size to %d", QEMU_NFS_MAX_READAHEAD_SIZE);
 569            client->readahead = QEMU_NFS_MAX_READAHEAD_SIZE;
 570        }
 571        nfs_set_readahead(client->context, client->readahead);
 572#ifdef LIBNFS_FEATURE_PAGECACHE
 573        nfs_set_pagecache_ttl(client->context, 0);
 574#endif
 575        client->cache_used = true;
 576    }
 577#endif
 578
 579#ifdef LIBNFS_FEATURE_PAGECACHE
 580    if (qemu_opt_get(opts, "page-cache-size")) {
 581        if (open_flags & BDRV_O_NOCACHE) {
 582            error_setg(errp, "Cannot enable NFS pagecache "
 583                             "if cache.direct = on");
 584            goto fail;
 585        }
 586        client->pagecache = qemu_opt_get_number(opts, "page-cache-size", 0);
 587        if (client->pagecache > QEMU_NFS_MAX_PAGECACHE_SIZE) {
 588            error_report("NFS Warning: Truncating NFS pagecache "
 589                         "size to %d pages", QEMU_NFS_MAX_PAGECACHE_SIZE);
 590            client->pagecache = QEMU_NFS_MAX_PAGECACHE_SIZE;
 591        }
 592        nfs_set_pagecache(client->context, client->pagecache);
 593        nfs_set_pagecache_ttl(client->context, 0);
 594        client->cache_used = true;
 595    }
 596#endif
 597
 598#ifdef LIBNFS_FEATURE_DEBUG
 599    if (qemu_opt_get(opts, "debug")) {
 600        client->debug = qemu_opt_get_number(opts, "debug", 0);
 601        /* limit the maximum debug level to avoid potential flooding
 602         * of our log files. */
 603        if (client->debug > QEMU_NFS_MAX_DEBUG_LEVEL) {
 604            error_report("NFS Warning: Limiting NFS debug level "
 605                         "to %d", QEMU_NFS_MAX_DEBUG_LEVEL);
 606            client->debug = QEMU_NFS_MAX_DEBUG_LEVEL;
 607        }
 608        nfs_set_debug(client->context, client->debug);
 609    }
 610#endif
 611
 612    ret = nfs_mount(client->context, client->server->host, client->path);
 613    if (ret < 0) {
 614        error_setg(errp, "Failed to mount nfs share: %s",
 615                   nfs_get_error(client->context));
 616        goto fail;
 617    }
 618
 619    if (flags & O_CREAT) {
 620        ret = nfs_creat(client->context, file, 0600, &client->fh);
 621        if (ret < 0) {
 622            error_setg(errp, "Failed to create file: %s",
 623                       nfs_get_error(client->context));
 624            goto fail;
 625        }
 626    } else {
 627        ret = nfs_open(client->context, file, flags, &client->fh);
 628        if (ret < 0) {
 629            error_setg(errp, "Failed to open file : %s",
 630                       nfs_get_error(client->context));
 631            goto fail;
 632        }
 633    }
 634
 635    ret = nfs_fstat(client->context, client->fh, &st);
 636    if (ret < 0) {
 637        error_setg(errp, "Failed to fstat file: %s",
 638                   nfs_get_error(client->context));
 639        goto fail;
 640    }
 641
 642    ret = DIV_ROUND_UP(st.st_size, BDRV_SECTOR_SIZE);
 643    client->st_blocks = st.st_blocks;
 644    client->has_zero_init = S_ISREG(st.st_mode);
 645    *strp = '/';
 646    goto out;
 647
 648fail:
 649    nfs_client_close(client);
 650out:
 651    qemu_opts_del(opts);
 652    g_free(file);
 653    return ret;
 654}
 655
 656static int nfs_file_open(BlockDriverState *bs, QDict *options, int flags,
 657                         Error **errp) {
 658    NFSClient *client = bs->opaque;
 659    int64_t ret;
 660
 661    client->aio_context = bdrv_get_aio_context(bs);
 662
 663    ret = nfs_client_open(client, options,
 664                          (flags & BDRV_O_RDWR) ? O_RDWR : O_RDONLY,
 665                          errp, bs->open_flags);
 666    if (ret < 0) {
 667        return ret;
 668    }
 669
 670    bs->total_sectors = ret;
 671    ret = 0;
 672    return ret;
 673}
 674
 675static QemuOptsList nfs_create_opts = {
 676    .name = "nfs-create-opts",
 677    .head = QTAILQ_HEAD_INITIALIZER(nfs_create_opts.head),
 678    .desc = {
 679        {
 680            .name = BLOCK_OPT_SIZE,
 681            .type = QEMU_OPT_SIZE,
 682            .help = "Virtual disk size"
 683        },
 684        { /* end of list */ }
 685    }
 686};
 687
 688static int nfs_file_create(const char *url, QemuOpts *opts, Error **errp)
 689{
 690    int ret = 0;
 691    int64_t total_size = 0;
 692    NFSClient *client = g_new0(NFSClient, 1);
 693    QDict *options = NULL;
 694
 695    client->aio_context = qemu_get_aio_context();
 696
 697    /* Read out options */
 698    total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
 699                          BDRV_SECTOR_SIZE);
 700
 701    options = qdict_new();
 702    ret = nfs_parse_uri(url, options, errp);
 703    if (ret < 0) {
 704        goto out;
 705    }
 706
 707    ret = nfs_client_open(client, options, O_CREAT, errp, 0);
 708    if (ret < 0) {
 709        goto out;
 710    }
 711    ret = nfs_ftruncate(client->context, client->fh, total_size);
 712    nfs_client_close(client);
 713out:
 714    QDECREF(options);
 715    g_free(client);
 716    return ret;
 717}
 718
 719static int nfs_has_zero_init(BlockDriverState *bs)
 720{
 721    NFSClient *client = bs->opaque;
 722    return client->has_zero_init;
 723}
 724
 725/* Called (via nfs_service) with QemuMutex held.  */
 726static void
 727nfs_get_allocated_file_size_cb(int ret, struct nfs_context *nfs, void *data,
 728                               void *private_data)
 729{
 730    NFSRPC *task = private_data;
 731    task->ret = ret;
 732    if (task->ret == 0) {
 733        memcpy(task->st, data, sizeof(struct stat));
 734    }
 735    if (task->ret < 0) {
 736        error_report("NFS Error: %s", nfs_get_error(nfs));
 737    }
 738    task->complete = 1;
 739    bdrv_wakeup(task->bs);
 740}
 741
 742static int64_t nfs_get_allocated_file_size(BlockDriverState *bs)
 743{
 744    NFSClient *client = bs->opaque;
 745    NFSRPC task = {0};
 746    struct stat st;
 747
 748    if (bdrv_is_read_only(bs) &&
 749        !(bs->open_flags & BDRV_O_NOCACHE)) {
 750        return client->st_blocks * 512;
 751    }
 752
 753    task.bs = bs;
 754    task.st = &st;
 755    if (nfs_fstat_async(client->context, client->fh, nfs_get_allocated_file_size_cb,
 756                        &task) != 0) {
 757        return -ENOMEM;
 758    }
 759
 760    nfs_set_events(client);
 761    BDRV_POLL_WHILE(bs, !task.complete);
 762
 763    return (task.ret < 0 ? task.ret : st.st_blocks * 512);
 764}
 765
 766static int nfs_file_truncate(BlockDriverState *bs, int64_t offset)
 767{
 768    NFSClient *client = bs->opaque;
 769    return nfs_ftruncate(client->context, client->fh, offset);
 770}
 771
 772/* Note that this will not re-establish a connection with the NFS server
 773 * - it is effectively a NOP.  */
 774static int nfs_reopen_prepare(BDRVReopenState *state,
 775                              BlockReopenQueue *queue, Error **errp)
 776{
 777    NFSClient *client = state->bs->opaque;
 778    struct stat st;
 779    int ret = 0;
 780
 781    if (state->flags & BDRV_O_RDWR && bdrv_is_read_only(state->bs)) {
 782        error_setg(errp, "Cannot open a read-only mount as read-write");
 783        return -EACCES;
 784    }
 785
 786    if ((state->flags & BDRV_O_NOCACHE) && client->cache_used) {
 787        error_setg(errp, "Cannot disable cache if libnfs readahead or"
 788                         " pagecache is enabled");
 789        return -EINVAL;
 790    }
 791
 792    /* Update cache for read-only reopens */
 793    if (!(state->flags & BDRV_O_RDWR)) {
 794        ret = nfs_fstat(client->context, client->fh, &st);
 795        if (ret < 0) {
 796            error_setg(errp, "Failed to fstat file: %s",
 797                       nfs_get_error(client->context));
 798            return ret;
 799        }
 800        client->st_blocks = st.st_blocks;
 801    }
 802
 803    return 0;
 804}
 805
 806static void nfs_refresh_filename(BlockDriverState *bs, QDict *options)
 807{
 808    NFSClient *client = bs->opaque;
 809    QDict *opts = qdict_new();
 810    QObject *server_qdict;
 811    Visitor *ov;
 812
 813    qdict_put_str(opts, "driver", "nfs");
 814
 815    if (client->uid && !client->gid) {
 816        snprintf(bs->exact_filename, sizeof(bs->exact_filename),
 817                 "nfs://%s%s?uid=%" PRId64, client->server->host, client->path,
 818                 client->uid);
 819    } else if (!client->uid && client->gid) {
 820        snprintf(bs->exact_filename, sizeof(bs->exact_filename),
 821                 "nfs://%s%s?gid=%" PRId64, client->server->host, client->path,
 822                 client->gid);
 823    } else if (client->uid && client->gid) {
 824        snprintf(bs->exact_filename, sizeof(bs->exact_filename),
 825                 "nfs://%s%s?uid=%" PRId64 "&gid=%" PRId64,
 826                 client->server->host, client->path, client->uid, client->gid);
 827    } else {
 828        snprintf(bs->exact_filename, sizeof(bs->exact_filename),
 829                 "nfs://%s%s", client->server->host, client->path);
 830    }
 831
 832    ov = qobject_output_visitor_new(&server_qdict);
 833    visit_type_NFSServer(ov, NULL, &client->server, &error_abort);
 834    visit_complete(ov, &server_qdict);
 835    qdict_put_obj(opts, "server", server_qdict);
 836    qdict_put_str(opts, "path", client->path);
 837
 838    if (client->uid) {
 839        qdict_put_int(opts, "user", client->uid);
 840    }
 841    if (client->gid) {
 842        qdict_put_int(opts, "group", client->gid);
 843    }
 844    if (client->tcp_syncnt) {
 845        qdict_put_int(opts, "tcp-syn-cnt", client->tcp_syncnt);
 846    }
 847    if (client->readahead) {
 848        qdict_put_int(opts, "readahead-size", client->readahead);
 849    }
 850    if (client->pagecache) {
 851        qdict_put_int(opts, "page-cache-size", client->pagecache);
 852    }
 853    if (client->debug) {
 854        qdict_put_int(opts, "debug", client->debug);
 855    }
 856
 857    visit_free(ov);
 858    qdict_flatten(opts);
 859    bs->full_open_options = opts;
 860}
 861
 862#ifdef LIBNFS_FEATURE_PAGECACHE
 863static void nfs_invalidate_cache(BlockDriverState *bs,
 864                                 Error **errp)
 865{
 866    NFSClient *client = bs->opaque;
 867    nfs_pagecache_invalidate(client->context, client->fh);
 868}
 869#endif
 870
 871static BlockDriver bdrv_nfs = {
 872    .format_name                    = "nfs",
 873    .protocol_name                  = "nfs",
 874
 875    .instance_size                  = sizeof(NFSClient),
 876    .bdrv_parse_filename            = nfs_parse_filename,
 877    .create_opts                    = &nfs_create_opts,
 878
 879    .bdrv_has_zero_init             = nfs_has_zero_init,
 880    .bdrv_get_allocated_file_size   = nfs_get_allocated_file_size,
 881    .bdrv_truncate                  = nfs_file_truncate,
 882
 883    .bdrv_file_open                 = nfs_file_open,
 884    .bdrv_close                     = nfs_file_close,
 885    .bdrv_create                    = nfs_file_create,
 886    .bdrv_reopen_prepare            = nfs_reopen_prepare,
 887
 888    .bdrv_co_preadv                 = nfs_co_preadv,
 889    .bdrv_co_pwritev                = nfs_co_pwritev,
 890    .bdrv_co_flush_to_disk          = nfs_co_flush,
 891
 892    .bdrv_detach_aio_context        = nfs_detach_aio_context,
 893    .bdrv_attach_aio_context        = nfs_attach_aio_context,
 894    .bdrv_refresh_filename          = nfs_refresh_filename,
 895
 896#ifdef LIBNFS_FEATURE_PAGECACHE
 897    .bdrv_invalidate_cache          = nfs_invalidate_cache,
 898#endif
 899};
 900
 901static void nfs_block_init(void)
 902{
 903    bdrv_register(&bdrv_nfs);
 904}
 905
 906block_init(nfs_block_init);
 907