From e87ea9c085f8cd46af2c1ddfddbd383aa04ac9bb Mon Sep 17 00:00:00 2001 From: Xavier Deguillard Date: Thu, 4 Mar 2021 09:57:01 -0800 Subject: [PATCH] nfs: implement the WRITE RPC Summary: This simply writes the data passed in to the inode. Note that the current implementation has a protocol violation since it doesn't sync the data written to disk but advertise to the client that it did. This is completely wrong from a data consistency guarantee, but is probably fine for now. Once the code becomes closer to being production ready, this will be changed to honor the client asks. Reviewed By: kmancini Differential Revision: D26681614 fbshipit-source-id: 82ad7a141be3bbe365363b1f6692ae62f253423f --- eden/fs/inodes/NfsDispatcherImpl.cpp | 15 ++++++ eden/fs/inodes/NfsDispatcherImpl.h | 6 +++ eden/fs/nfs/NfsDispatcher.h | 25 ++++++++++ eden/fs/nfs/Nfsd3.cpp | 69 ++++++++++++++++++++++++++-- eden/fs/nfs/NfsdRpc.h | 3 +- 5 files changed, 113 insertions(+), 5 deletions(-) diff --git a/eden/fs/inodes/NfsDispatcherImpl.cpp b/eden/fs/inodes/NfsDispatcherImpl.cpp index 18b3d05780..83c93e5879 100644 --- a/eden/fs/inodes/NfsDispatcherImpl.cpp +++ b/eden/fs/inodes/NfsDispatcherImpl.cpp @@ -65,6 +65,21 @@ folly::Future NfsDispatcherImpl::readlink( }); } +folly::Future NfsDispatcherImpl::write( + InodeNumber ino, + std::unique_ptr data, + off_t offset, + ObjectFetchContext& /*context*/) { + return inodeMap_->lookupFileInode(ino).thenValue( + [data = std::move(data), offset](const FileInodePtr& inode) mutable { + // TODO(xavierd): Modify write to obtain pre and post stat of the file. + return inode->write(std::move(data), offset) + .thenValue([](size_t written) { + return WriteRes{written, std::nullopt, std::nullopt}; + }); + }); +} + folly::Future NfsDispatcherImpl::create( InodeNumber dir, PathComponent name, diff --git a/eden/fs/inodes/NfsDispatcherImpl.h b/eden/fs/inodes/NfsDispatcherImpl.h index c156cc3765..c3548ee791 100644 --- a/eden/fs/inodes/NfsDispatcherImpl.h +++ b/eden/fs/inodes/NfsDispatcherImpl.h @@ -36,6 +36,12 @@ class NfsDispatcherImpl : public NfsDispatcher { InodeNumber ino, ObjectFetchContext& context) override; + folly::Future write( + InodeNumber ino, + std::unique_ptr data, + off_t offset, + ObjectFetchContext& context) override; + folly::Future create( InodeNumber ino, PathComponent name, diff --git a/eden/fs/nfs/NfsDispatcher.h b/eden/fs/nfs/NfsDispatcher.h index bc6fe6e979..e8961f3f12 100644 --- a/eden/fs/nfs/NfsDispatcher.h +++ b/eden/fs/nfs/NfsDispatcher.h @@ -64,6 +64,31 @@ class NfsDispatcher { InodeNumber ino, ObjectFetchContext& context) = 0; + /** + * Return value of the write method. + */ + struct WriteRes { + /** Number of bytes written */ + size_t written; + + /** Attributes of the directory prior to creating the file */ + std::optional preStat; + /** Attributes of the directory after creating the file */ + std::optional postStat; + }; + + /** + * Write data at offset to the file referenced by the InodeNumber ino. + * + * See the comment on the create method below for the meaning of the returned + * pre and post stat. + */ + virtual folly::Future write( + InodeNumber ino, + std::unique_ptr data, + off_t offset, + ObjectFetchContext& context) = 0; + /** * Return value of the create method. */ diff --git a/eden/fs/nfs/Nfsd3.cpp b/eden/fs/nfs/Nfsd3.cpp index ad2cd0869d..27ee2b00e4 100644 --- a/eden/fs/nfs/Nfsd3.cpp +++ b/eden/fs/nfs/Nfsd3.cpp @@ -475,12 +475,75 @@ folly::Future Nfsd3ServerProcessor::read( return folly::unit; } +/** + * Generate a unique per-EdenFS instance write cookie. + * + * TODO(xavierd): Note that for now this will always be 0 as this is to handle + * the case where the server restart while the client isn't aware. + */ +writeverf3 makeWriteVerf() { + return 0; +} + folly::Future Nfsd3ServerProcessor::write( - folly::io::Cursor /*deser*/, + folly::io::Cursor deser, folly::io::QueueAppender ser, uint32_t xid) { - serializeReply(ser, accept_stat::PROC_UNAVAIL, xid); - return folly::unit; + serializeReply(ser, accept_stat::SUCCESS, xid); + + auto args = XdrTrait::deserialize(deser); + + static auto context = + ObjectFetchContext::getNullContextWithCauseDetail("write"); + + // I have no idea why NFS sent us data that we shouldn't write to the file, + // but here it is, let's only take up to count bytes from the data. + auto queue = folly::IOBufQueue(); + queue.append(std::move(args.data)); + auto data = queue.split(args.count); + + return dispatcher_ + ->write(args.file.ino, std::move(data), args.offset, *context) + .thenTry([ser = std::move(ser)]( + folly::Try writeTry) mutable { + if (writeTry.hasException()) { + WRITE3res res{ + {{exceptionToNfsError(writeTry.exception()), WRITE3resfail{}}}}; + XdrTrait::serialize(ser, res); + } else { + auto writeRes = std::move(writeTry).value(); + + // NFS is limited to writing a maximum of 4GB (2^32) of data + // per write call, so despite write returning a size_t, it + // should always fit in a uint32_t. + XDCHECK_LE( + writeRes.written, size_t{std::numeric_limits::max()}); + + WRITE3res res{ + {{nfsstat3::NFS3_OK, + WRITE3resok{ + wcc_data{ + /*before*/ writeRes.preStat.has_value() + ? statToPreOpAttr(writeRes.preStat.value()) + : pre_op_attr{}, + /*after*/ writeRes.postStat.has_value() + ? post_op_attr{statToFattr3( + writeRes.postStat.value())} + : post_op_attr{}}, + /*count*/ folly::to_narrow(writeRes.written), + // TODO(xavierd): the following is a total lie and we + // should call inode->fdatasync() in the case where + // args.stable is anything other than + // stable_how::UNSTABLE. For testing purpose, this is + // OK. + /*committed*/ stable_how::FILE_SYNC, + /*verf*/ makeWriteVerf(), + }}}}; + XdrTrait::serialize(ser, res); + } + + return folly::unit; + }); } /** diff --git a/eden/fs/nfs/NfsdRpc.h b/eden/fs/nfs/NfsdRpc.h index 145a096b10..9163a029a3 100644 --- a/eden/fs/nfs/NfsdRpc.h +++ b/eden/fs/nfs/NfsdRpc.h @@ -379,8 +379,7 @@ struct READLINK3res // WRITE Procedure: -constexpr inline size_t NFS3_WRITEVERFSIZE = 8; -using writeverf3 = std::array; +using writeverf3 = uint64_t; enum class stable_how { UNSTABLE = 0, DATA_SYNC = 1, FILE_SYNC = 2 };