LibNFS currently support NFS version 3 only.
Signed-off-by: Peter Lieven <address@hidden>
---
v3->v4:
- finally added full implementation of bdrv_get_allocated_file_size [Stefan]
- removed trailing \n from error statements [Stefan]
v2->v3:
- rebased the stefanha/block
- use pkg_config to check for libnfs (ignoring cflags which are broken in
1.8.0) [Stefan]
- fixed NFSClient declaration [Stefan]
- renamed Task variables to task [Stefan]
- renamed NFSTask to NFSRPC [Ronnie]
- do not update bs->total_sectors in nfs_co_writev [Stefan]
- return -ENOMEM on all async call failures [Stefan,Ronnie]
- fully implement ftruncate
- use util/uri.c for URL parsing [Stefan]
- reworked nfs_file_open_common to nfs_client_open which works on NFSClient
[Stefan]
- added a comment ot the connect message that libnfs support NFSv3 only at
the moment.
- DID NOT add full implementation of bdrv_get_allocated_file_size because
we are not in a coroutine context and I cannot do an async call here.
I could do a sync call if there would be a guarantee that no requests
are in flight. [Stefan]
v1->v2:
- fixed block/Makefile.objs [Ronnie]
- do not always register a read handler [Ronnie]
- add support for reading beyond EOF [Fam]
- fixed struct and paramter naming [Fam]
- fixed overlong lines and whitespace errors [Fam]
- return return status from libnfs whereever possible [Fam]
- added comment why we set allocated_file_size to -ENOTSUP after write [Fam]
- avoid segfault when parsing filname [Fam]
- remove unused close_bh from NFSClient [Fam]
- avoid dividing and mutliplying total_size by BDRV_SECTOR_SIZE in
nfs_file_create [Fam]
MAINTAINERS | 5 +
block/Makefile.objs | 1 +
block/nfs.c | 414 +++++++++++++++++++++++++++++++++++++++++++++++++++
configure | 26 ++++
4 files changed, 446 insertions(+)
create mode 100644 block/nfs.c
diff --git a/MAINTAINERS b/MAINTAINERS
index a5ab8f8..09996ab 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -935,6 +935,11 @@ M: Peter Lieven <address@hidden>
S: Supported
F: block/iscsi.c
+NFS
+M: Peter Lieven <address@hidden>
+S: Maintained
+F: block/nfs.c
+
SSH
M: Richard W.M. Jones <address@hidden>
S: Supported
diff --git a/block/Makefile.objs b/block/Makefile.objs
index 4e8c91e..e254a21 100644
--- a/block/Makefile.objs
+++ b/block/Makefile.objs
@@ -12,6 +12,7 @@ block-obj-$(CONFIG_LINUX_AIO) += linux-aio.o
ifeq ($(CONFIG_POSIX),y)
block-obj-y += nbd.o nbd-client.o sheepdog.o
block-obj-$(CONFIG_LIBISCSI) += iscsi.o
+block-obj-$(CONFIG_LIBNFS) += nfs.o
block-obj-$(CONFIG_CURL) += curl.o
block-obj-$(CONFIG_RBD) += rbd.o
block-obj-$(CONFIG_GLUSTERFS) += gluster.o
diff --git a/block/nfs.c b/block/nfs.c
new file mode 100644
index 0000000..78fd8a1
--- /dev/null
+++ b/block/nfs.c
@@ -0,0 +1,414 @@
+/*
+ * QEMU Block driver for native access to files on NFS shares
+ *
+ * Copyright (c) 2013 Peter Lieven <address@hidden>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "config-host.h"
+
+#include <poll.h>
+#include "qemu-common.h"
+#include "qemu/config-file.h"
+#include "qemu/error-report.h"
+#include "block/block_int.h"
+#include "trace.h"
+#include "qemu/iov.h"
+#include "qemu/uri.h"
+#include "sysemu/sysemu.h"
+
+#include <nfsc/libnfs-zdr.h>
+#include <nfsc/libnfs.h>
+#include <nfsc/libnfs-raw.h>
+#include <nfsc/libnfs-raw-mount.h>
+
+typedef struct NFSClient {
+ struct nfs_context *context;
+ struct nfsfh *fh;
+ int events;
+ bool has_zero_init;
+} NFSClient;
+
+typedef struct NFSRPC {
+ int status;
+ int complete;
+ QEMUIOVector *iov;
+ struct stat *st;
+ Coroutine *co;
+ QEMUBH *bh;
+} NFSRPC;
+
+static void nfs_process_read(void *arg);
+static void nfs_process_write(void *arg);
+
+static void nfs_set_events(NFSClient *client)
+{
+ int ev = nfs_which_events(client->context);
+ if (ev != client->events) {
+ qemu_aio_set_fd_handler(nfs_get_fd(client->context),
+ (ev & POLLIN) ? nfs_process_read : NULL,
+ (ev & POLLOUT) ? nfs_process_write : NULL,
+ client);
+
+ }
+ client->events = ev;
+}
+
+static void nfs_process_read(void *arg)
+{
+ NFSClient *client = arg;
+ nfs_service(client->context, POLLIN);
+ nfs_set_events(client);
+}
+
+static void nfs_process_write(void *arg)
+{
+ NFSClient *client = arg;
+ nfs_service(client->context, POLLOUT);
+ nfs_set_events(client);
+}
+
+static void nfs_co_init_task(NFSClient *client, NFSRPC *task)
+{
+ *task = (NFSRPC) {
+ .co = qemu_coroutine_self(),
+ };
+}
+
+static void nfs_co_generic_bh_cb(void *opaque)
+{
+ NFSRPC *task = opaque;
+ qemu_bh_delete(task->bh);
+ qemu_coroutine_enter(task->co, NULL);
+}
+
+static void
+nfs_co_generic_cb(int status, struct nfs_context *nfs, void *data,
+ void *private_data)
+{
+ NFSRPC *task = private_data;
+ task->complete = 1;
+ task->status = status;
+ if (task->status > 0 && task->iov) {
+ if (task->status <= task->iov->size) {
+ qemu_iovec_from_buf(task->iov, 0, data, task->status);
+ } else {
+ task->status = -EIO;
+ }
+ }
+ if (task->status == 0 && task->st) {
+ memcpy(task->st, data, sizeof(struct stat));
+ }
+ if (task->co) {
+ task->bh = qemu_bh_new(nfs_co_generic_bh_cb, task);
+ qemu_bh_schedule(task->bh);
+ }
+}
+
+static int coroutine_fn nfs_co_readv(BlockDriverState *bs,
+ int64_t sector_num, int nb_sectors,
+ QEMUIOVector *iov)
+{
+ NFSClient *client = bs->opaque;
+ NFSRPC task;
+
+ nfs_co_init_task(client, &task);
+ task.iov = iov;
+
+ if (nfs_pread_async(client->context, client->fh,
+ sector_num * BDRV_SECTOR_SIZE,
+ nb_sectors * BDRV_SECTOR_SIZE,
+ nfs_co_generic_cb, &task) != 0) {
+ return -ENOMEM;
+ }
+
+ while (!task.complete) {
+ nfs_set_events(client);
+ qemu_coroutine_yield();
+ }
+
+ if (task.status < 0) {
+ return task.status;
+ }
+
+ return 0;
+}
+
+static int coroutine_fn nfs_co_writev(BlockDriverState *bs,
+ int64_t sector_num, int nb_sectors,
+ QEMUIOVector *iov)
+{
+ NFSClient *client = bs->opaque;
+ NFSRPC task;
+ char *buf = NULL;
+
+ nfs_co_init_task(client, &task);
+
+ buf = g_malloc(nb_sectors * BDRV_SECTOR_SIZE);
+ qemu_iovec_to_buf(iov, 0, buf, nb_sectors * BDRV_SECTOR_SIZE);
+
+ if (nfs_pwrite_async(client->context, client->fh,
+ sector_num * BDRV_SECTOR_SIZE,
+ nb_sectors * BDRV_SECTOR_SIZE,
+ buf, nfs_co_generic_cb, &task) != 0) {
+ g_free(buf);
+ return -ENOMEM;
+ }
+
+ while (!task.complete) {
+ nfs_set_events(client);
+ qemu_coroutine_yield();
+ }
+
+ g_free(buf);
+
+ if (task.status != nb_sectors * BDRV_SECTOR_SIZE) {
+ return task.status < 0 ? task.status : -EIO;
+ }
+
+ return 0;
+}
+
+static int coroutine_fn nfs_co_flush(BlockDriverState *bs)
+{
+ NFSClient *client = bs->opaque;
+ NFSRPC task;
+
+ nfs_co_init_task(client, &task);
+
+ if (nfs_fsync_async(client->context, client->fh, nfs_co_generic_cb,
+ &task) != 0) {
+ return -ENOMEM;
+ }
+
+ while (!task.complete) {
+ nfs_set_events(client);
+ qemu_coroutine_yield();
+ }
+
+ return task.status;
+}
+
+static QemuOptsList runtime_opts = {
+ .name = "nfs",
+ .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
+ .desc = {
+ {
+ .name = "filename",
+ .type = QEMU_OPT_STRING,
+ .help = "URL to the NFS file",
+ },
+ { /* end of list */ }
+ },
+};
+
+static void nfs_client_close(NFSClient *client)
+{
+ if (client->context) {
+ if (client->fh) {
+ nfs_close(client->context, client->fh);
+ }
+ qemu_aio_set_fd_handler(nfs_get_fd(client->context), NULL, NULL, NULL);
+ nfs_destroy_context(client->context);
+ }
+ memset(client, 0, sizeof(NFSClient));
+}
+
+static void nfs_file_close(BlockDriverState *bs)
+{
+ NFSClient *client = bs->opaque;
+ nfs_client_close(client);
+}
+
+static int64_t nfs_client_open(NFSClient *client, const char *filename,
+ int flags, Error **errp)
+{
+ int ret = -EINVAL;
+ URI *uri;
+ char *file = NULL, *strp = NULL;
+ struct stat st;
+
+ uri = uri_parse(filename);
+ if (!uri) {
+ error_setg(errp, "Invalid URL specified.");
+ goto fail;
+ }
+ strp = strrchr(uri->path, '/');
+ if (strp == NULL) {
+ error_setg(errp, "Invalid URL specified.");
+ goto fail;
+ }
+ file = g_strdup(strp);
+ *strp = 0;
+
+ client->context = nfs_init_context();
+ if (client->context == NULL) {
+ error_setg(errp, "Failed to init NFS context");
+ goto fail;
+ }
+
+ ret = nfs_mount(client->context, uri->server, uri->path);
+ if (ret < 0) {
+ error_setg(errp, "Failed to mount nfs share: %s",
+ nfs_get_error(client->context));
+ goto fail;
+ }
+
+ if (flags & O_CREAT) {
+ ret = nfs_creat(client->context, file, 0600, &client->fh);
+ if (ret < 0) {
+ error_setg(errp, "Failed to create file: %s",
+ nfs_get_error(client->context));
+ goto fail;
+ }
+ } else {
+ ret = nfs_open(client->context, file, flags, &client->fh);
+ if (ret < 0) {
+ error_setg(errp, "Failed to open file : %s",
+ nfs_get_error(client->context));
+ goto fail;
+ }
+ }
+
+ ret = nfs_fstat(client->context, client->fh, &st);
+ if (ret < 0) {
+ error_setg(errp, "Failed to fstat file: %s",
+ nfs_get_error(client->context));
+ goto fail;
+ }
+
+ ret = DIV_ROUND_UP(st.st_size, BDRV_SECTOR_SIZE);
+ client->has_zero_init = S_ISREG(st.st_mode);
+ goto out;
+fail:
+ nfs_client_close(client);
+out:
+ uri_free(uri);
+ g_free(file);
+ return ret;
+}
+
+static int nfs_file_open(BlockDriverState *bs, QDict *options, int flags,
+ Error **errp) {
+ NFSClient *client = bs->opaque;
+ int64_t ret;
+ QemuOpts *opts;
+ Error *local_err = NULL;
+
+ opts = qemu_opts_create_nofail(&runtime_opts);
+ qemu_opts_absorb_qdict(opts, options, &local_err);
+ if (error_is_set(&local_err)) {
+ qerror_report_err(local_err);
+ error_free(local_err);
+ return -EINVAL;
+ }
+ ret = nfs_client_open(client, qemu_opt_get(opts, "filename"),
+ (flags & BDRV_O_RDWR) ? O_RDWR : O_RDONLY,
+ errp);
+ if (ret < 0) {
+ return ret;
+ }
+ bs->total_sectors = ret;
+ return 0;
+}
+
+static int nfs_file_create(const char *filename, QEMUOptionParameter *options,
+ Error **errp)
+{
+ int ret = 0;
+ int64_t total_size = 0;
+ NFSClient *client = g_malloc0(sizeof(NFSClient));
+
+ /* Read out options */
+ while (options && options->name) {
+ if (!strcmp(options->name, "size")) {
+ total_size = options->value.n;
+ }
+ options++;
+ }
+
+ ret = nfs_client_open(client, filename, O_CREAT, errp);
+ if (ret < 0) {
+ goto out;
+ }
+ ret = nfs_ftruncate(client->context, client->fh, total_size);
+out:
+ nfs_client_close(client);
+ g_free(client);
+ return ret;
+}
+
+static int nfs_has_zero_init(BlockDriverState *bs)
+{
+ NFSClient *client = bs->opaque;
+ return client->has_zero_init;
+}
+
+static int64_t nfs_get_allocated_file_size(BlockDriverState *bs)
+{
+ NFSClient *client = bs->opaque;
+ NFSRPC task = {0};
+ struct stat st;
+
+ task.st = &st;
+ if (nfs_fstat_async(client->context, client->fh, nfs_co_generic_cb,
+ &task) != 0) {
+ return -ENOMEM;
+ }
+
+ while (!task.complete) {
+ nfs_set_events(client);
+ qemu_aio_wait();
+ }
+
+ return (task.status < 0 ? task.status : st.st_blocks * st.st_blksize);
+}
+
+static int nfs_file_truncate(BlockDriverState *bs, int64_t offset)
+{
+ NFSClient *client = bs->opaque;
+ return nfs_ftruncate(client->context, client->fh, offset);
+}
+
+static BlockDriver bdrv_nfs = {
+ .format_name = "nfs",
+ .protocol_name = "nfs",
+
+ .instance_size = sizeof(NFSClient),
+ .bdrv_needs_filename = true,
+ .bdrv_has_zero_init = nfs_has_zero_init,
+ .bdrv_get_allocated_file_size = nfs_get_allocated_file_size,
+ .bdrv_truncate = nfs_file_truncate,
+
+ .bdrv_file_open = nfs_file_open,
+ .bdrv_close = nfs_file_close,
+ .bdrv_create = nfs_file_create,
+
+ .bdrv_co_readv = nfs_co_readv,
+ .bdrv_co_writev = nfs_co_writev,
+ .bdrv_co_flush_to_disk = nfs_co_flush,
+};
+
+static void nfs_block_init(void)
+{
+ bdrv_register(&bdrv_nfs);
+}
+
+block_init(nfs_block_init);
diff --git a/configure b/configure
index d97556c..bae1d2b 100755
--- a/configure
+++ b/configure
@@ -251,6 +251,7 @@ vss_win32_sdk=""
win_sdk="no"
want_tools="yes"
libiscsi=""
+libnfs=""
coroutine=""
coroutine_pool=""
seccomp=""
@@ -839,6 +840,10 @@ for opt do
;;
--enable-libiscsi) libiscsi="yes"
;;
+ --disable-libnfs) libnfs="no"
+ ;;
+ --enable-libnfs) libnfs="yes"
+ ;;
--enable-profiler) profiler="yes"
;;
--disable-cocoa) cocoa="no"
@@ -1210,6 +1215,8 @@ echo " --enable-spice enable spice"
echo " --enable-rbd enable building the rados block device (rbd)"
echo " --disable-libiscsi disable iscsi support"
echo " --enable-libiscsi enable iscsi support"
+echo " --disable-libnfs disable nfs support"
+echo " --enable-libnfs enable nfs support"
echo " --disable-smartcard-nss disable smartcard nss support"
echo " --enable-smartcard-nss enable smartcard nss support"
echo " --disable-libusb disable libusb (for usb passthrough)"
@@ -3596,6 +3603,20 @@ elif test "$debug" = "no" ; then
CFLAGS="-O2 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 $CFLAGS"
fi
+##########################################
+# Do we have libnfs
+if test "$libnfs" != "no" ; then
+ if $pkg_config --atleast-version=1.8.0 libnfs; then
+ libnfs="yes"
+ libnfs_libs=$($pkg_config --libs libnfs)
+ LIBS="$LIBS $libnfs_libs"
+ else
+ if test "$libnfs" = "yes" ; then
+ feature_not_found "libnfs"
+ fi
+ libnfs="no"
+ fi
+fi
# Disable zero malloc errors for official releases unless explicitly told to
# enable/disable
@@ -3825,6 +3846,7 @@ echo "libiscsi support $libiscsi (1.4.0)"
else
echo "libiscsi support $libiscsi"
fi
+echo "libnfs support $libnfs"
echo "build guest agent $guest_agent"
echo "QGA VSS support $guest_agent_with_vss"
echo "seccomp support $seccomp"
@@ -4161,6 +4183,10 @@ if test "$libiscsi" = "yes" ; then
fi
fi
+if test "$libnfs" = "yes" ; then
+ echo "CONFIG_LIBNFS=y" >> $config_host_mak
+fi
+
if test "$seccomp" = "yes"; then
echo "CONFIG_SECCOMP=y" >> $config_host_mak
fi