From 7d0b8c97865cb6a2ab1f534305913343de78ac50 Mon Sep 17 00:00:00 2001 From: Pawel Dziepak Date: Wed, 27 Jun 2012 22:54:27 +0200 Subject: [PATCH] nfs4: Add support for file locking --- .../kernel/file_systems/nfs4/Cookie.cpp | 12 + src/add-ons/kernel/file_systems/nfs4/Cookie.h | 20 ++ .../kernel/file_systems/nfs4/Inode.cpp | 205 ++++++++++++++++++ src/add-ons/kernel/file_systems/nfs4/Inode.h | 9 + .../kernel/file_systems/nfs4/NFS4Defs.h | 38 ++++ .../kernel/file_systems/nfs4/NFS4Server.cpp | 36 ++- .../kernel/file_systems/nfs4/NFS4Server.h | 1 + .../file_systems/nfs4/ReplyInterpreter.cpp | 54 +++++ .../file_systems/nfs4/ReplyInterpreter.h | 5 + .../file_systems/nfs4/RequestBuilder.cpp | 92 ++++++++ .../kernel/file_systems/nfs4/RequestBuilder.h | 8 + .../file_systems/nfs4/kernel_interface.cpp | 62 ++++++ 12 files changed, 541 insertions(+), 1 deletion(-) diff --git a/src/add-ons/kernel/file_systems/nfs4/Cookie.cpp b/src/add-ons/kernel/file_systems/nfs4/Cookie.cpp index 2cf9f0d9d4..a539c256e8 100644 --- a/src/add-ons/kernel/file_systems/nfs4/Cookie.cpp +++ b/src/add-ons/kernel/file_systems/nfs4/Cookie.cpp @@ -84,3 +84,15 @@ Cookie::CancelAll() return B_OK; } + +OpenFileCookie::OpenFileCookie() +{ + mutex_init(&fLocksLock, NULL); +} + + +OpenFileCookie::~OpenFileCookie() +{ + mutex_destroy(&fLocksLock); +} + diff --git a/src/add-ons/kernel/file_systems/nfs4/Cookie.h b/src/add-ons/kernel/file_systems/nfs4/Cookie.h index 82fcdc4439..c1d3b07af9 100644 --- a/src/add-ons/kernel/file_systems/nfs4/Cookie.h +++ b/src/add-ons/kernel/file_systems/nfs4/Cookie.h @@ -14,6 +14,20 @@ #include "Filesystem.h" +struct LockInfo { + uint32 fStateId[3]; + uint32 fStateSeq; + + uint32 fSequence; + + uint32 fOwner; + uint64 fStart; + uint64 fLength; + LockType fType; + + LockInfo* fNext; +}; + struct Cookie { struct RequestEntry { RPC::Request* fRequest; @@ -46,8 +60,14 @@ struct OpenFileCookie : public Cookie { uint64 fOwnerId; static vint64 fLastOwnerId; + LockInfo* fLocks; + mutex fLocksLock; + OpenFileCookie* fNext; OpenFileCookie* fPrev; + + OpenFileCookie(); + ~OpenFileCookie(); }; struct OpenDirCookie : public Cookie { diff --git a/src/add-ons/kernel/file_systems/nfs4/Inode.cpp b/src/add-ons/kernel/file_systems/nfs4/Inode.cpp index 9779993966..2f589eb675 100644 --- a/src/add-ons/kernel/file_systems/nfs4/Inode.cpp +++ b/src/add-ons/kernel/file_systems/nfs4/Inode.cpp @@ -699,6 +699,7 @@ Inode::Create(const char* name, int mode, int perms, OpenFileCookie* cookie, cookie->fMode = mode; cookie->fSequence = 0; + cookie->fLocks = NULL; Filehandle fh; do { @@ -803,6 +804,7 @@ Inode::Open(int mode, OpenFileCookie* cookie) cookie->fHandle = fHandle; cookie->fMode = mode; cookie->fSequence = 0; + cookie->fLocks = NULL; do { cookie->fClientId = fFilesystem->NFSServer()->ClientId(); @@ -1275,6 +1277,208 @@ Inode::ReadDir(void* _buffer, uint32 size, uint32* _count, } +status_t +Inode::TestLock(OpenFileCookie* cookie, struct flock* lock) +{ + do { + RPC::Server* serv = fFilesystem->Server(); + Request request(serv); + RequestBuilder& req = request.Builder(); + + req.PutFH(fHandle); + req.LockT(sGetLockType(lock->l_type, false), lock->l_start, + lock->l_len, cookie); + + status_t result = request.Send(); + if (result != B_OK) + return result; + + ReplyInterpreter &reply = request.Reply(); + if (_HandleErrors(reply.NFS4Error(), serv, cookie)) + continue; + + reply.PutFH(); + LockType ltype; + uint64 pos, len; + result = reply.LockT(&pos, &len, <ype); + if (reply.NFS4Error() == NFS4ERR_DENIED) { + lock->l_type = sLockTypeToHaiku(ltype); + lock->l_start = static_cast(pos); + lock->l_len = static_cast(len); + result = B_OK; + } else if (reply.NFS4Error() == NFS4_OK) + lock->l_type = F_UNLCK; + + return result; + } while (true); + + return B_OK; +} + + +status_t +Inode::AcquireLock(OpenFileCookie* cookie, const struct flock* lock, + bool wait) +{ + LockInfo* linfo = new LockInfo; + if (linfo == NULL) + return B_NO_MEMORY; + + linfo->fSequence = 0; + linfo->fStart = lock->l_start; + if (lock->l_len == OFF_MAX) + linfo->fLength = UINT64_MAX; + else + linfo->fLength = lock->l_len; + linfo->fType = sGetLockType(lock->l_type, wait); + linfo->fOwner = find_thread(NULL); + + do { + RPC::Server* serv = fFilesystem->Server(); + Request request(serv); + RequestBuilder& req = request.Builder(); + + req.PutFH(fHandle); + req.Lock(cookie, linfo); + + status_t result = request.Send(); + if (result != B_OK) { + delete linfo; + return result; + } + + ReplyInterpreter &reply = request.Reply(); + if (wait && reply.NFS4Error() == NFS4ERR_DENIED) { + snooze_etc(5 * 1000000, B_SYSTEM_TIMEBASE, B_RELATIVE_TIMEOUT); + continue; + } + + if (_HandleErrors(reply.NFS4Error(), serv, cookie)) + continue; + + reply.PutFH(); + result = reply.Lock(linfo); + if (result != B_OK) { + delete linfo; + return result; + } + + break; + } while (true); + + mutex_lock(&cookie->fLocksLock); + linfo->fNext = cookie->fLocks; + cookie->fLocks = linfo; + mutex_unlock(&cookie->fLocksLock); + + return B_OK; +} + + +status_t +Inode::ReleaseLock(OpenFileCookie* cookie, const struct flock* lock) +{ + LockInfo* prev = NULL; + uint32 owner = find_thread(NULL); + + mutex_lock(&cookie->fLocksLock); + LockInfo* linfo = cookie->fLocks; + while (linfo != NULL) { + if (linfo->fOwner == owner && + linfo->fStart == static_cast(lock->l_start) && + (linfo->fLength == static_cast(lock->l_len) || + (linfo->fLength == UINT64_MAX && lock->l_len == OFF_MAX))) { + if (prev != NULL) + prev->fNext = linfo->fNext; + else + cookie->fLocks = linfo->fNext; + break; + } + + prev = linfo; + linfo = linfo->fNext; + } + mutex_unlock(&cookie->fLocksLock); + + if (linfo == NULL) + return B_BAD_VALUE; + + do { + RPC::Server* serv = fFilesystem->Server(); + Request request(serv); + RequestBuilder& req = request.Builder(); + + req.PutFH(fHandle); + req.LockU(linfo); + + status_t result = request.Send(); + if (result != B_OK) + return result; + + ReplyInterpreter &reply = request.Reply(); + + if (_HandleErrors(reply.NFS4Error(), serv, cookie)) + continue; + + reply.PutFH(); + result = reply.LockU(); + if (result != B_OK) + return result; + + break; + } while (true); + + delete linfo; + + return B_OK; +} + + +status_t +Inode::ReleaseAllLocks(OpenFileCookie* cookie) +{ + mutex_lock(&cookie->fLocksLock); + while (cookie->fLocks != NULL) { + do { + RPC::Server* serv = fFilesystem->Server(); + Request request(serv); + RequestBuilder& req = request.Builder(); + + req.PutFH(fHandle); + req.LockU(cookie->fLocks); + + status_t result = request.Send(); + if (result != B_OK) { + mutex_unlock(&cookie->fLocksLock); + return result; + } + + ReplyInterpreter &reply = request.Reply(); + + if (_HandleErrors(reply.NFS4Error(), serv, cookie)) + continue; + + reply.PutFH(); + result = reply.LockU(); + if (result != B_OK) { + mutex_unlock(&cookie->fLocksLock); + return result; + } + + break; + } while (true); + + LockInfo* linfo = cookie->fLocks->fNext; + delete cookie->fLocks; + cookie->fLocks = linfo; + } + mutex_unlock(&cookie->fLocksLock); + + return B_OK; +} + + + bool Inode::_HandleErrors(uint32 nfs4Error, RPC::Server* serv, OpenFileCookie* cookie) @@ -1284,6 +1488,7 @@ Inode::_HandleErrors(uint32 nfs4Error, RPC::Server* serv, return false; // server needs more time, we need to wait + case NFS4ERR_LOCKED: case NFS4ERR_DELAY: if (cookie == NULL || (cookie->fMode & O_NONBLOCK) == 0) { snooze_etc(5 * 1000000, B_SYSTEM_TIMEBASE, B_RELATIVE_TIMEOUT); diff --git a/src/add-ons/kernel/file_systems/nfs4/Inode.h b/src/add-ons/kernel/file_systems/nfs4/Inode.h index 88fd1f9f73..1cf1f11dce 100644 --- a/src/add-ons/kernel/file_systems/nfs4/Inode.h +++ b/src/add-ons/kernel/file_systems/nfs4/Inode.h @@ -59,6 +59,15 @@ public: status_t ReadDir(void* buffer, uint32 size, uint32* count, OpenDirCookie* cookie); + status_t TestLock(OpenFileCookie* cookie, + struct flock* lock); + status_t AcquireLock(OpenFileCookie* cookie, + const struct flock* lock, bool wait); + status_t ReleaseLock(OpenFileCookie* cookie, + const struct flock* lock); + status_t ReleaseAllLocks(OpenFileCookie* cookie); + + private: Inode(); diff --git a/src/add-ons/kernel/file_systems/nfs4/NFS4Defs.h b/src/add-ons/kernel/file_systems/nfs4/NFS4Defs.h index 2268d56fc1..11cb1f8abc 100644 --- a/src/add-ons/kernel/file_systems/nfs4/NFS4Defs.h +++ b/src/add-ons/kernel/file_systems/nfs4/NFS4Defs.h @@ -9,6 +9,7 @@ #define NFS4DEFS_H +#include #include #include @@ -27,6 +28,9 @@ enum Opcode { OpGetAttr = 9, OpGetFH = 10, OpLink = 11, + OpLock = 12, + OpLockT = 13, + OpLockU = 14, OpLookUp = 15, OpLookUpUp = 16, OpNverify = 17, @@ -180,6 +184,40 @@ enum WriteStable { FILE_SYNC4 = 2 }; +enum LockType { + READ_LT = 1, + WRITE_LT = 2, + READW_LT = 3, + WRITEW_LT = 4 +}; + + +static inline +LockType sGetLockType(short type, bool wait) { + switch (type) { + case F_RDLCK: return wait ? READW_LT : READ_LT; + case F_WRLCK: return wait ? WRITEW_LT : WRITE_LT; + default: return READ_LT; + } +} + + +static inline +short sLockTypeToHaiku(LockType type) { + switch (type) { + case READ_LT: + case READW_LT: + return F_RDLCK; + + case WRITE_LT: + case WRITEW_LT: + return F_WRLCK; + + default: return F_UNLCK; + } +} + + enum Errors { NFS4_OK = 0, NFS4ERR_PERM = 1, diff --git a/src/add-ons/kernel/file_systems/nfs4/NFS4Server.cpp b/src/add-ons/kernel/file_systems/nfs4/NFS4Server.cpp index 5a74bd94cb..f8689842db 100644 --- a/src/add-ons/kernel/file_systems/nfs4/NFS4Server.cpp +++ b/src/add-ons/kernel/file_systems/nfs4/NFS4Server.cpp @@ -51,6 +51,7 @@ NFS4Server::ServerRebooted(uint64 clientId) OpenFileCookie* current = fOpenFiles; while (current != NULL) { _ReclaimOpen(current); + _ReclaimLocks(current); current = current->fNext; } mutex_unlock(&fOpenLock); @@ -74,7 +75,7 @@ NFS4Server::_ReclaimOpen(OpenFileCookie* cookie) req.Open(CLAIM_PREVIOUS, cookie->fSequence++, OPEN4_SHARE_ACCESS_READ, cookie->fClientId, OPEN4_NOCREATE, cookie->fOwnerId, NULL); - status_t result = request.Send();; + status_t result = request.Send(); if (result != B_OK) return result; @@ -108,6 +109,39 @@ NFS4Server::_ReclaimOpen(OpenFileCookie* cookie) } +status_t +NFS4Server::_ReclaimLocks(OpenFileCookie* cookie) +{ + mutex_lock(&cookie->fLocksLock); + LockInfo* linfo = cookie->fLocks; + while (linfo != NULL) { + do { + Request request(fServer); + RequestBuilder& req = request.Builder(); + + req.PutFH(cookie->fHandle); + req.Lock(cookie, linfo, true); + + status_t result = request.Send(); + if (result != B_OK) + break; + + ReplyInterpreter &reply = request.Reply(); + + reply.PutFH(); + reply.Lock(linfo); + + break; + } while (true); + + linfo = linfo->fNext; + } + mutex_unlock(&cookie->fLocksLock); + + return B_OK; +} + + void NFS4Server::AddOpenFile(OpenFileCookie* cookie) { diff --git a/src/add-ons/kernel/file_systems/nfs4/NFS4Server.h b/src/add-ons/kernel/file_systems/nfs4/NFS4Server.h index ebe83d2ade..264e34af3e 100644 --- a/src/add-ons/kernel/file_systems/nfs4/NFS4Server.h +++ b/src/add-ons/kernel/file_systems/nfs4/NFS4Server.h @@ -31,6 +31,7 @@ public: inline uint32 LeaseTime(); private: status_t _ReclaimOpen(OpenFileCookie* cookie); + status_t _ReclaimLocks(OpenFileCookie* cookie); status_t _GetLeaseTime(); diff --git a/src/add-ons/kernel/file_systems/nfs4/ReplyInterpreter.cpp b/src/add-ons/kernel/file_systems/nfs4/ReplyInterpreter.cpp index 6955182e85..dadabcb779 100644 --- a/src/add-ons/kernel/file_systems/nfs4/ReplyInterpreter.cpp +++ b/src/add-ons/kernel/file_systems/nfs4/ReplyInterpreter.cpp @@ -13,6 +13,8 @@ #include +#include "Cookie.h" + FSLocation::~FSLocation() { @@ -208,6 +210,56 @@ ReplyInterpreter::Link() } +status_t +ReplyInterpreter::Lock(LockInfo* linfo) +{ + status_t res = _OperationError(OpLock); + if (res != B_OK) + return res; + + linfo->fStateSeq = fReply->Stream().GetUInt(); + linfo->fStateId[0] = fReply->Stream().GetUInt(); + linfo->fStateId[1] = fReply->Stream().GetUInt(); + linfo->fStateId[2] = fReply->Stream().GetUInt(); + + return fReply->Stream().IsEOF() ? B_BAD_VALUE : B_OK; +} + + +status_t +ReplyInterpreter::LockT(uint64* pos, uint64* len, LockType* type) +{ + status_t res = _OperationError(OpLockU); + if (res != B_WOULD_BLOCK || NFS4Error() != NFS4ERR_DENIED) + return res; + + *pos = fReply->Stream().GetUHyper(); + *len = fReply->Stream().GetUHyper(); + *type = static_cast(fReply->Stream().GetInt()); + + fReply->Stream().GetUHyper(); + fReply->Stream().GetOpaque(NULL); + + return fReply->Stream().IsEOF() ? B_BAD_VALUE : B_OK; +} + + +status_t +ReplyInterpreter::LockU() +{ + status_t res = _OperationError(OpLockU); + if (res != B_OK) + return res; + + fReply->Stream().GetUInt(); + fReply->Stream().GetUInt(); + fReply->Stream().GetUInt(); + fReply->Stream().GetUInt(); + + return fReply->Stream().IsEOF() ? B_BAD_VALUE : B_OK; +} + + status_t ReplyInterpreter::Open(uint32* id, uint32* seq, bool* confirm) { @@ -675,6 +727,8 @@ ReplyInterpreter::_NFS4ErrorToHaiku(uint32 x) case NFS4ERR_FBIG: return B_FILE_TOO_LARGE; // ... case NFS4ERR_DELAY: + case NFS4ERR_DENIED: + case NFS4ERR_LOCKED: case NFS4ERR_GRACE: return B_WOULD_BLOCK; // ... diff --git a/src/add-ons/kernel/file_systems/nfs4/ReplyInterpreter.h b/src/add-ons/kernel/file_systems/nfs4/ReplyInterpreter.h index aa504a5592..b96499f995 100644 --- a/src/add-ons/kernel/file_systems/nfs4/ReplyInterpreter.h +++ b/src/add-ons/kernel/file_systems/nfs4/ReplyInterpreter.h @@ -54,6 +54,8 @@ struct DirEntry { ~DirEntry(); }; +class LockInfo; + class ReplyInterpreter { public: ReplyInterpreter(RPC::Reply* reply = NULL); @@ -70,6 +72,9 @@ public: status_t GetAttr(AttrValue** attrs, uint32* count); status_t GetFH(Filehandle* fh); status_t Link(); + status_t Lock(LockInfo* linfo); + status_t LockT(uint64* pos, uint64* len, LockType* type); + status_t LockU(); inline status_t LookUp(); inline status_t LookUpUp(); inline status_t Nverify(); diff --git a/src/add-ons/kernel/file_systems/nfs4/RequestBuilder.cpp b/src/add-ons/kernel/file_systems/nfs4/RequestBuilder.cpp index a3108aa8b6..9fd61fb8bf 100644 --- a/src/add-ons/kernel/file_systems/nfs4/RequestBuilder.cpp +++ b/src/add-ons/kernel/file_systems/nfs4/RequestBuilder.cpp @@ -11,6 +11,8 @@ #include +#include "Cookie.h" + RequestBuilder::RequestBuilder(Procedure proc) : @@ -146,6 +148,96 @@ RequestBuilder::GetFH() } +status_t +RequestBuilder::Lock(OpenFileCookie* cookie, LockInfo* lock, bool reclaim) +{ + if (fProcedure != ProcCompound) + return B_BAD_VALUE; + if (fRequest == NULL) + return B_NO_MEMORY; + + fRequest->Stream().AddUInt(OpLock); + + fRequest->Stream().AddInt(lock->fType); + fRequest->Stream().AddBoolean(reclaim); + + fRequest->Stream().AddUHyper(lock->fStart); + fRequest->Stream().AddUHyper(lock->fLength); + + fRequest->Stream().AddBoolean(true); // new lock owner + + // open seq stateid + fRequest->Stream().AddUInt(cookie->fSequence++); + fRequest->Stream().AddUInt(cookie->fStateSeq); + fRequest->Stream().AddUInt(cookie->fStateId[0]); + fRequest->Stream().AddUInt(cookie->fStateId[1]); + fRequest->Stream().AddUInt(cookie->fStateId[2]); + + // lock seq owner + fRequest->Stream().AddUInt(lock->fSequence++); + fRequest->Stream().AddUHyper(cookie->fClientId); + fRequest->Stream().AddOpaque(&lock->fOwner, sizeof(lock->fOwner)); + + fOpCount++; + + return B_OK; +} + + +status_t +RequestBuilder::LockT(LockType type, uint64 pos, uint64 len, + OpenFileCookie* cookie) +{ + if (fProcedure != ProcCompound) + return B_BAD_VALUE; + if (fRequest == NULL) + return B_NO_MEMORY; + + fRequest->Stream().AddUInt(OpLockT); + + fRequest->Stream().AddInt(type); + + fRequest->Stream().AddUHyper(pos); + fRequest->Stream().AddUHyper(len); + + fRequest->Stream().AddUHyper(cookie->fClientId); + + uint32 owner = find_thread(NULL); + fRequest->Stream().AddOpaque(&owner, sizeof(owner)); + + fOpCount++; + + return B_OK; +} + + +status_t +RequestBuilder::LockU(LockInfo* lock) +{ + if (fProcedure != ProcCompound) + return B_BAD_VALUE; + if (fRequest == NULL) + return B_NO_MEMORY; + + fRequest->Stream().AddUInt(OpLockU); + + fRequest->Stream().AddInt(lock->fType); + + fRequest->Stream().AddUInt(lock->fSequence++); + fRequest->Stream().AddUInt(lock->fStateSeq); + fRequest->Stream().AddUInt(lock->fStateId[0]); + fRequest->Stream().AddUInt(lock->fStateId[1]); + fRequest->Stream().AddUInt(lock->fStateId[2]); + + fRequest->Stream().AddUHyper(lock->fStart); + fRequest->Stream().AddUHyper(lock->fLength); + + fOpCount++; + + return B_OK; +} + + status_t RequestBuilder::Link(const char* name) { diff --git a/src/add-ons/kernel/file_systems/nfs4/RequestBuilder.h b/src/add-ons/kernel/file_systems/nfs4/RequestBuilder.h index 805d7005fb..68dbb418f1 100644 --- a/src/add-ons/kernel/file_systems/nfs4/RequestBuilder.h +++ b/src/add-ons/kernel/file_systems/nfs4/RequestBuilder.h @@ -18,6 +18,9 @@ #include "XDR.h" +class OpenFileCookie; +class LockInfo; + class RequestBuilder { public: RequestBuilder(Procedure p = ProcCompound); @@ -34,6 +37,11 @@ public: status_t GetAttr(Attribute* attrs, uint32 count); status_t GetFH(); status_t Link(const char* name); + status_t Lock(OpenFileCookie* cookie, + LockInfo* lock, bool reclaim = false); + status_t LockT(LockType type, uint64 pos, + uint64 len, OpenFileCookie* cookie); + status_t LockU(LockInfo* lock); status_t LookUp(const char* name); status_t LookUpUp(); status_t Nverify(AttrValue* attr, uint32 count); diff --git a/src/add-ons/kernel/file_systems/nfs4/kernel_interface.cpp b/src/add-ons/kernel/file_systems/nfs4/kernel_interface.cpp index b4b2d77769..dc1e07e72c 100644 --- a/src/add-ons/kernel/file_systems/nfs4/kernel_interface.cpp +++ b/src/add-ons/kernel/file_systems/nfs4/kernel_interface.cpp @@ -466,6 +466,39 @@ nfs4_rewind_dir(fs_volume* volume, fs_vnode* vnode, void* _cookie) } +static status_t +nfs4_test_lock(fs_volume* volume, fs_vnode* vnode, void* _cookie, + struct flock* lock) +{ + Inode* inode = reinterpret_cast(vnode->private_node); + OpenFileCookie* cookie = reinterpret_cast(_cookie); + return inode->TestLock(cookie, lock); +} + + +static status_t +nfs4_acquire_lock(fs_volume* volume, fs_vnode* vnode, void* _cookie, + const struct flock* lock, bool wait) +{ + Inode* inode = reinterpret_cast(vnode->private_node); + OpenFileCookie* cookie = reinterpret_cast(_cookie); + return inode->AcquireLock(cookie, lock, wait); +} + + +static status_t +nfs4_release_lock(fs_volume* volume, fs_vnode* vnode, void* _cookie, + const struct flock* lock) +{ + Inode* inode = reinterpret_cast(vnode->private_node); + OpenFileCookie* cookie = reinterpret_cast(_cookie); + if (lock != NULL) + return inode->ReleaseLock(cookie, lock); + else + return inode->ReleaseAllLocks(cookie); +} + + status_t nfs4_init() { @@ -561,6 +594,35 @@ fs_vnode_ops gNFSv4VnodeOps = { nfs4_free_dir_cookie, nfs4_read_dir, nfs4_rewind_dir, + + /* attribute directory operations */ + NULL, // open_attr_dir + NULL, // close_attr_dir + NULL, // free_attr_dir_cookie + NULL, // read_attr_dir + NULL, // rewind_attr_dir + + /* attribute operations */ + NULL, // create_attr + NULL, // open_attr + NULL, // close_attr + NULL, // free_attr_cookie + NULL, // read_attr + NULL, // write_attr + + NULL, // read_attr_stat + NULL, // write_attr_stat + NULL, // rename_attr + NULL, // remove_attr + + /* support for node and FS layers */ + NULL, // create_special_node + NULL, // get_super_vnode + + /* lock operations */ + nfs4_test_lock, + nfs4_acquire_lock, + nfs4_release_lock, }; static file_system_module_info sNFSv4ModuleInfo = {