/* * Copyright 2012-2020 Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Paweł Dziepak, pdziepak@quarnos.org */ #include "ReplyInterpreter.h" #include #include #include #include "Cookie.h" #define ERROR(x...) dprintf("nfs4: " x) #ifdef DEBUG #define TRACE(x...) dprintf("nfs4: " x) #define CALLED() dprintf("nfs4: called %s", __func__) #else #define TRACE(x...) #define CALLED() #endif static status_t ProcessStream(RPC::Reply* reply, const char* callName) { status_t result = reply->Stream().IsEOF() ? B_BAD_VALUE : B_OK; if (result != B_OK) TRACE("call %s failed!\n", callName); return result; } FSLocation::~FSLocation() { CALLED(); if (fRootPath != NULL) { for (uint32 i = 0; fRootPath[i] != NULL; i++) free(const_cast(fRootPath[i])); } delete[] fRootPath; for (uint32 i = 0; i < fCount; i++) free(const_cast(fLocations[i])); delete[] fLocations; } FSLocations::~FSLocations() { CALLED(); if (fRootPath != NULL) { for (uint32 i = 0; fRootPath[i] != NULL; i++) free(const_cast(fRootPath[i])); } delete[] fRootPath; delete[] fLocations; } AttrValue::AttrValue() : fAttribute(0), fFreePointer(false) { } AttrValue::~AttrValue() { CALLED(); if (fFreePointer) free(fData.fPointer); if (fAttribute == FATTR4_FS_LOCATIONS) delete fData.fLocations; } DirEntry::DirEntry() : fName(NULL), fAttrs(NULL), fAttrCount(0) { } DirEntry::~DirEntry() { free(const_cast(fName)); delete[] fAttrs; } ReplyInterpreter::ReplyInterpreter(RPC::Reply* reply) : fNFS4Error(NFS4_OK), fDecodeError(false), fReply(reply) { CALLED(); if (reply != NULL) _ParseHeader(); } ReplyInterpreter::~ReplyInterpreter() { delete fReply; } void ReplyInterpreter::_ParseHeader() { CALLED(); fNFS4Error = fReply->Stream().GetUInt(); fReply->Stream().GetOpaque(NULL); fReply->Stream().GetUInt(); } status_t ReplyInterpreter::Access(uint32* supported, uint32* allowed) { CALLED(); status_t res = _OperationError(OpAccess); if (res != B_OK) return res; uint32 support = fReply->Stream().GetUInt(); uint32 allow = fReply->Stream().GetUInt(); if (supported != NULL) *supported = support; if (allowed != NULL) *allowed = allow; return ProcessStream(fReply, __func__); } status_t ReplyInterpreter::Close() { CALLED(); status_t res = _OperationError(OpClose); if (res != B_OK) return res; fReply->Stream().GetUInt(); fReply->Stream().GetUInt(); fReply->Stream().GetUInt(); fReply->Stream().GetUInt(); return ProcessStream(fReply, __func__); } status_t ReplyInterpreter::Commit() { CALLED(); status_t res = _OperationError(OpCommit); if (res != B_OK) return res; fReply->Stream().GetOpaque(NULL); return ProcessStream(fReply, __func__); } status_t ReplyInterpreter::Create(uint64* before, uint64* after, bool& atomic) { CALLED(); status_t res = _OperationError(OpCreate); if (res != B_OK) return res; atomic = fReply->Stream().GetBoolean(); *before = fReply->Stream().GetUHyper(); *after = fReply->Stream().GetUHyper(); uint32 count = fReply->Stream().GetUInt(); for (uint32 i = 0; i < count; i++) fReply->Stream().GetUInt(); return ProcessStream(fReply, __func__); } // Bit Twiddling Hacks // http://graphics.stanford.edu/~seander/bithacks.html static inline uint32 CountBits(uint32 v) { CALLED(); v = v - ((v >> 1) & 0x55555555); v = (v & 0x33333333) + ((v >> 2) & 0x33333333); return (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24; } status_t ReplyInterpreter::GetAttr(AttrValue** attrs, uint32* count) { CALLED(); status_t res = _OperationError(OpGetAttr); if (res != B_OK) return res; return _DecodeAttrs(fReply->Stream(), attrs, count); } status_t ReplyInterpreter::GetFH(FileHandle* fh) { CALLED(); status_t res = _OperationError(OpGetFH); if (res != B_OK) return res; uint32 size; const void* ptr = fReply->Stream().GetOpaque(&size); if (ptr == NULL || size > NFS4_FHSIZE) { ERROR("Unable to %s!\n", __func__); return B_BAD_VALUE; } if (fh != NULL) { fh->fSize = size; memcpy(fh->fData, ptr, size); } return ProcessStream(fReply, __func__); } status_t ReplyInterpreter::Link(uint64* before, uint64* after, bool& atomic) { CALLED(); status_t res = _OperationError(OpLink); if (res != B_OK) return res; atomic = fReply->Stream().GetBoolean(); *before = fReply->Stream().GetUHyper(); *after = fReply->Stream().GetUHyper(); return ProcessStream(fReply, __func__); } status_t ReplyInterpreter::Lock(LockInfo* linfo) { CALLED(); status_t res = _OperationError(OpLock); if (res != B_OK) return res; linfo->fOwner->fStateSeq = fReply->Stream().GetUInt(); linfo->fOwner->fStateId[0] = fReply->Stream().GetUInt(); linfo->fOwner->fStateId[1] = fReply->Stream().GetUInt(); linfo->fOwner->fStateId[2] = fReply->Stream().GetUInt(); return ProcessStream(fReply, __func__); } status_t ReplyInterpreter::LockT(uint64* pos, uint64* len, LockType* type) { CALLED(); status_t res = _OperationError(OpLockT); 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 ProcessStream(fReply, __func__); } status_t ReplyInterpreter::LockU(LockInfo* linfo) { CALLED(); status_t res = _OperationError(OpLockU); if (res != B_OK) return res; linfo->fOwner->fStateSeq = fReply->Stream().GetUInt(); linfo->fOwner->fStateId[0] = fReply->Stream().GetUInt(); linfo->fOwner->fStateId[1] = fReply->Stream().GetUInt(); linfo->fOwner->fStateId[2] = fReply->Stream().GetUInt(); return ProcessStream(fReply, __func__); } status_t ReplyInterpreter::Open(uint32* id, uint32* seq, bool* confirm, OpenDelegationData* delegData, ChangeInfo* changeInfo) { CALLED(); status_t res = _OperationError(OpOpen); if (res != B_OK) return res; *seq = fReply->Stream().GetUInt(); id[0] = fReply->Stream().GetUInt(); id[1] = fReply->Stream().GetUInt(); id[2] = fReply->Stream().GetUInt(); // change info bool atomic = fReply->Stream().GetBoolean(); uint64 before = fReply->Stream().GetUHyper(); uint64 after = fReply->Stream().GetUHyper(); if (changeInfo != NULL) { changeInfo->fAtomic = atomic; changeInfo->fBefore = before; changeInfo->fAfter = after; } uint32 flags = fReply->Stream().GetUInt(); *confirm = (flags & OPEN4_RESULT_CONFIRM) == OPEN4_RESULT_CONFIRM; // attrmask uint32 bcount = fReply->Stream().GetUInt(); for (uint32 i = 0; i < bcount; i++) fReply->Stream().GetUInt(); // delegation info uint32 delegation = fReply->Stream().GetUInt(); OpenDelegationData data; if (delegData == NULL) delegData = &data; if (delegation == OPEN_DELEGATE_NONE) { delegData->fType = OPEN_DELEGATE_NONE; return ProcessStream(fReply, __func__); } delegData->fStateSeq = fReply->Stream().GetUInt(); delegData->fStateID[0] = fReply->Stream().GetUInt(); delegData->fStateID[1] = fReply->Stream().GetUInt(); delegData->fStateID[2] = fReply->Stream().GetUInt(); delegData->fRecall = fReply->Stream().GetBoolean(); switch (delegation) { case OPEN_DELEGATE_READ: delegData->fType = OPEN_DELEGATE_READ; break; case OPEN_DELEGATE_WRITE: delegData->fType = OPEN_DELEGATE_WRITE; int32 limitBy = fReply->Stream().GetInt(); if (limitBy == NFS_LIMIT_SIZE) delegData->fSpaceLimit = fReply->Stream().GetUHyper(); else if (limitBy == NFS_LIMIT_BLOCKS) { uint32 numBlocks = fReply->Stream().GetUInt(); delegData->fSpaceLimit = fReply->Stream().GetUInt() * numBlocks; } break; } // ACE data fReply->Stream().GetUInt(); fReply->Stream().GetUInt(); fReply->Stream().GetUInt(); fReply->Stream().GetOpaque(NULL); return ProcessStream(fReply, __func__); } status_t ReplyInterpreter::OpenConfirm(uint32* stateSeq) { CALLED(); status_t res = _OperationError(OpOpenConfirm); if (res != B_OK) return res; *stateSeq = fReply->Stream().GetUInt(); fReply->Stream().GetUInt(); fReply->Stream().GetUInt(); fReply->Stream().GetUInt(); return ProcessStream(fReply, __func__); } status_t ReplyInterpreter::Read(void* buffer, uint32* size, bool* eof) { CALLED(); status_t res = _OperationError(OpRead); if (res != B_OK) return res; *eof = fReply->Stream().GetBoolean(); const void* ptr = fReply->Stream().GetOpaque(size); memcpy(buffer, ptr, *size); return ProcessStream(fReply, __func__); } status_t ReplyInterpreter::ReadDir(uint64* cookie, uint64* cookieVerf, DirEntry** dirents, uint32* _count, bool* eof) { CALLED(); status_t res = _OperationError(OpReadDir); if (res != B_OK) return res; *cookieVerf = fReply->Stream().GetUHyper(); bool isNext; uint32 count = 0; // TODO: using list instead of array would make this much more elegant // and efficient XDR::Stream::Position dataStart = fReply->Stream().Current(); isNext = fReply->Stream().GetBoolean(); while (isNext) { fReply->Stream().GetUHyper(); free(fReply->Stream().GetString()); AttrValue* values; uint32 attrCount; _DecodeAttrs(fReply->Stream(), &values, &attrCount); delete[] values; count++; isNext = fReply->Stream().GetBoolean(); } DirEntry* entries = new(std::nothrow) DirEntry[count]; if (entries == NULL) return B_NO_MEMORY; count = 0; fReply->Stream().SetPosition(dataStart); isNext = fReply->Stream().GetBoolean(); while (isNext) { *cookie = fReply->Stream().GetUHyper(); entries[count].fName = fReply->Stream().GetString(); _DecodeAttrs(fReply->Stream(), &entries[count].fAttrs, &entries[count].fAttrCount); count++; isNext = fReply->Stream().GetBoolean(); } *eof = fReply->Stream().GetBoolean(); *_count = count; *dirents = entries; if (fReply->Stream().IsEOF()) { delete[] entries; ERROR("Unable to %s!\n", __func__); return B_BAD_VALUE; } return B_OK; } status_t ReplyInterpreter::ReadLink(void* buffer, uint32* size, uint32 maxSize) { CALLED(); status_t res = _OperationError(OpReadLink); if (res != B_OK) return res; const void* ptr = fReply->Stream().GetOpaque(size); memcpy(buffer, ptr, min_c(*size, maxSize)); return ProcessStream(fReply, __func__); } status_t ReplyInterpreter::Remove(uint64* before, uint64* after, bool& atomic) { CALLED(); status_t res = _OperationError(OpRemove); if (res != B_OK) return res; atomic = fReply->Stream().GetBoolean(); *before = fReply->Stream().GetUHyper(); *after = fReply->Stream().GetUHyper(); return ProcessStream(fReply, __func__); } status_t ReplyInterpreter::Rename(uint64* fromBefore, uint64* fromAfter, bool& fromAtomic, uint64* toBefore, uint64* toAfter, bool& toAtomic) { CALLED(); status_t res = _OperationError(OpRename); if (res != B_OK) return res; fromAtomic = fReply->Stream().GetBoolean(); *fromBefore = fReply->Stream().GetUHyper(); *fromAfter = fReply->Stream().GetUHyper(); toAtomic = fReply->Stream().GetBoolean(); *toBefore = fReply->Stream().GetUHyper(); *toAfter = fReply->Stream().GetUHyper(); return ProcessStream(fReply, __func__); } status_t ReplyInterpreter::SetAttr() { CALLED(); status_t res = _OperationError(OpSetAttr); if (res != B_OK) return res; uint32 bcount = fReply->Stream().GetUInt(); for (uint32 i = 0; i < bcount; i++) fReply->Stream().GetUInt(); return ProcessStream(fReply, __func__); } status_t ReplyInterpreter::SetClientID(uint64* clientid, uint64* verifier) { CALLED(); status_t res = _OperationError(OpSetClientID); if (res != B_OK) return res; *clientid = fReply->Stream().GetUHyper(); *verifier = fReply->Stream().GetUHyper(); return ProcessStream(fReply, __func__); } status_t ReplyInterpreter::Write(uint32* size) { CALLED(); status_t res = _OperationError(OpWrite); if (res != B_OK) return res; *size = fReply->Stream().GetUInt(); fReply->Stream().GetInt(); fReply->Stream().GetUHyper(); return ProcessStream(fReply, __func__); } const char** ReplyInterpreter::_GetPath(XDR::ReadStream& stream) { CALLED(); uint32 count = stream.GetUInt(); char** path = new char*[count + 1]; if (path == NULL) return NULL; uint32 i; for (i = 0; i < count; i++) { path[i] = stream.GetString(); if (path[i] == NULL) goto out; } path[count] = NULL; return const_cast(path); out: for (uint32 j = 0; j < i; j++) free(path[i]); delete[] path; return NULL; } status_t ReplyInterpreter::_DecodeAttrs(XDR::ReadStream& str, AttrValue** attrs, uint32* count) { CALLED(); uint32 bcount = fReply->Stream().GetUInt(); uint32* bitmap = new(std::nothrow) uint32[bcount]; if (bitmap == NULL) return B_NO_MEMORY; ArrayDeleter _(bitmap); uint32 attr_count = 0; for (uint32 i = 0; i < bcount; i++) { bitmap[i] = str.GetUInt(); attr_count += CountBits(bitmap[i]); } if (attr_count == 0) { *attrs = NULL; *count = 0; return B_OK; } else if (attr_count > FATTR4_MAXIMUM_ATTR_ID) { ERROR("too many attr!\n"); return B_BAD_VALUE; } uint32 size; const void* ptr = str.GetOpaque(&size); XDR::ReadStream stream(const_cast(ptr), size); AttrValue* values = new(std::nothrow) AttrValue[attr_count]; if (values == NULL) return B_NO_MEMORY; uint32 current = 0; if (sIsAttrSet(FATTR4_SUPPORTED_ATTRS, bitmap, bcount)) { values[current].fAttribute = FATTR4_SUPPORTED_ATTRS; uint32 count = stream.GetInt(); uint32 i; // two uint32 are enough for NFS4, not for NFS4.1 for (i = 0; i < min_c(count, 2); i++) ((uint32*)&values[current].fData.fValue64)[i] = stream.GetUInt(); for (; i < count; i++) stream.GetUInt(); current++; } if (sIsAttrSet(FATTR4_TYPE, bitmap, bcount)) { values[current].fAttribute = FATTR4_TYPE; values[current].fData.fValue32 = stream.GetInt(); current++; } if (sIsAttrSet(FATTR4_FH_EXPIRE_TYPE, bitmap, bcount)) { values[current].fAttribute = FATTR4_FH_EXPIRE_TYPE; values[current].fData.fValue32 = stream.GetUInt(); current++; } if (sIsAttrSet(FATTR4_CHANGE, bitmap, bcount)) { values[current].fAttribute = FATTR4_CHANGE; values[current].fData.fValue64 = stream.GetUHyper(); current++; } if (sIsAttrSet(FATTR4_SIZE, bitmap, bcount)) { values[current].fAttribute = FATTR4_SIZE; values[current].fData.fValue64 = stream.GetUHyper(); current++; } if (sIsAttrSet(FATTR4_FSID, bitmap, bcount)) { values[current].fAttribute = FATTR4_FSID; values[current].fFreePointer = true; FileSystemId fsid; fsid.fMajor = stream.GetUHyper(); fsid.fMinor = stream.GetUHyper(); values[current].fData.fPointer = malloc(sizeof(fsid)); memcpy(values[current].fData.fPointer, &fsid, sizeof(fsid)); current++; } if (sIsAttrSet(FATTR4_LEASE_TIME, bitmap, bcount)) { values[current].fAttribute = FATTR4_LEASE_TIME; values[current].fData.fValue32 = stream.GetUInt(); current++; } if (sIsAttrSet(FATTR4_FILEID, bitmap, bcount)) { values[current].fAttribute = FATTR4_FILEID; values[current].fData.fValue64 = stream.GetUHyper(); current++; } if (sIsAttrSet(FATTR4_FILES_FREE, bitmap, bcount)) { values[current].fAttribute = FATTR4_FILES_FREE; values[current].fData.fValue64 = stream.GetUHyper(); current++; } if (sIsAttrSet(FATTR4_FILES_TOTAL, bitmap, bcount)) { values[current].fAttribute = FATTR4_FILES_TOTAL; values[current].fData.fValue64 = stream.GetUHyper(); current++; } if (sIsAttrSet(FATTR4_FS_LOCATIONS, bitmap, bcount)) { values[current].fAttribute = FATTR4_FS_LOCATIONS; FSLocations* locs = new FSLocations; locs->fRootPath = _GetPath(stream); locs->fCount = stream.GetUInt(); locs->fLocations = new FSLocation[locs->fCount]; for (uint32 i = 0; i < locs->fCount; i++) { locs->fLocations[i].fRootPath = _GetPath(stream); locs->fLocations[i].fCount = stream.GetUInt(); locs->fLocations[i].fLocations = new const char*[locs->fLocations[i].fCount]; for (uint32 j = 0; j < locs->fLocations[i].fCount; j++) locs->fLocations[i].fLocations[j] = stream.GetString(); } values[current].fData.fLocations = locs; current++; } if (sIsAttrSet(FATTR4_MAXREAD, bitmap, bcount)) { values[current].fAttribute = FATTR4_MAXREAD; values[current].fData.fValue64 = stream.GetUHyper(); current++; } if (sIsAttrSet(FATTR4_MAXWRITE, bitmap, bcount)) { values[current].fAttribute = FATTR4_MAXWRITE; values[current].fData.fValue64 = stream.GetUHyper(); current++; } if (sIsAttrSet(FATTR4_MODE, bitmap, bcount)) { values[current].fAttribute = FATTR4_MODE; values[current].fData.fValue32 = stream.GetUInt(); current++; } if (sIsAttrSet(FATTR4_NUMLINKS, bitmap, bcount)) { values[current].fAttribute = FATTR4_NUMLINKS; values[current].fData.fValue32 = stream.GetUInt(); current++; } if (sIsAttrSet(FATTR4_OWNER, bitmap, bcount)) { values[current].fAttribute = FATTR4_OWNER; values[current].fFreePointer = true; values[current].fData.fPointer = stream.GetString(); current++; } if (sIsAttrSet(FATTR4_OWNER_GROUP, bitmap, bcount)) { values[current].fAttribute = FATTR4_OWNER_GROUP; values[current].fFreePointer = true; values[current].fData.fPointer = stream.GetString(); current++; } if (sIsAttrSet(FATTR4_SPACE_FREE, bitmap, bcount)) { values[current].fAttribute = FATTR4_SPACE_FREE; values[current].fData.fValue64 = stream.GetUHyper(); current++; } if (sIsAttrSet(FATTR4_SPACE_TOTAL, bitmap, bcount)) { values[current].fAttribute = FATTR4_SPACE_TOTAL; values[current].fData.fValue64 = stream.GetUHyper(); current++; } if (sIsAttrSet(FATTR4_TIME_ACCESS, bitmap, bcount)) { values[current].fAttribute = FATTR4_TIME_ACCESS; values[current].fFreePointer = true; struct timespec ts; ts.tv_sec = static_cast(stream.GetHyper()); ts.tv_nsec = static_cast(stream.GetUInt()); values[current].fData.fPointer = malloc(sizeof(ts)); memcpy(values[current].fData.fPointer, &ts, sizeof(ts)); current++; } if (sIsAttrSet(FATTR4_TIME_CREATE, bitmap, bcount)) { values[current].fAttribute = FATTR4_TIME_CREATE; values[current].fFreePointer = true; struct timespec ts; ts.tv_sec = static_cast(stream.GetHyper()); ts.tv_nsec = static_cast(stream.GetUInt()); values[current].fData.fPointer = malloc(sizeof(ts)); memcpy(values[current].fData.fPointer, &ts, sizeof(ts)); current++; } if (sIsAttrSet(FATTR4_TIME_METADATA, bitmap, bcount)) { values[current].fAttribute = FATTR4_TIME_METADATA; values[current].fFreePointer = true; struct timespec ts; ts.tv_sec = static_cast(stream.GetHyper()); ts.tv_nsec = static_cast(stream.GetUInt()); values[current].fData.fPointer = malloc(sizeof(ts)); memcpy(values[current].fData.fPointer, &ts, sizeof(ts)); current++; } if (sIsAttrSet(FATTR4_TIME_MODIFY, bitmap, bcount)) { values[current].fAttribute = FATTR4_TIME_MODIFY; values[current].fFreePointer = true; struct timespec ts; ts.tv_sec = static_cast(stream.GetHyper()); ts.tv_nsec = static_cast(stream.GetUInt()); values[current].fData.fPointer = malloc(sizeof(ts)); memcpy(values[current].fData.fPointer, &ts, sizeof(ts)); current++; } *count = attr_count; *attrs = values; if (str.IsEOF()) { delete[] values; ERROR("call %s failed!\n", __func__); return B_BAD_VALUE; } return B_OK; } status_t ReplyInterpreter::_OperationError(Opcode op) { if (fDecodeError) { ERROR("Decode Error!\n"); return B_BAD_VALUE; } if (fReply == NULL) return B_NOT_INITIALIZED; if (fReply->Error() != B_OK || fReply->Stream().IsEOF()) { ERROR("Error not B_OK or empty stream!\n"); fDecodeError = true; return fReply->Error(); } if (fReply->Stream().GetInt() != op) { ERROR("Stream GetInt != op!\n"); fDecodeError = true; return B_BAD_VALUE; } status_t result = _NFS4ErrorToHaiku(fReply->Stream().GetUInt()); if (result != B_OK) { ERROR("NFS Error: %s\n", strerror(result)); fDecodeError = true; } return result; } status_t ReplyInterpreter::_NFS4ErrorToHaiku(uint32 x) { switch (x) { case NFS4_OK: return B_OK; case NFS4ERR_PERM: return B_PERMISSION_DENIED; case NFS4ERR_NOENT: return B_ENTRY_NOT_FOUND; case NFS4ERR_IO: return B_IO_ERROR; case NFS4ERR_NXIO: return B_DEVICE_NOT_FOUND; case NFS4ERR_ACCESS: return B_NOT_ALLOWED; case NFS4ERR_EXIST: return B_FILE_EXISTS; case NFS4ERR_XDEV: return B_CROSS_DEVICE_LINK; case NFS4ERR_NOTDIR: return B_NOT_A_DIRECTORY; case NFS4ERR_ISDIR: return B_IS_A_DIRECTORY; case NFS4ERR_INVAL: return B_BAD_VALUE; case NFS4ERR_FBIG: return B_FILE_TOO_LARGE; case NFS4ERR_NOTSUPP: return B_UNSUPPORTED; case NFS4ERR_ROFS: return B_READ_ONLY_DEVICE; case NFS4ERR_NAMETOOLONG: return B_NAME_TOO_LONG; case NFS4ERR_NOTEMPTY: return B_DIRECTORY_NOT_EMPTY; // ... case NFS4ERR_DELAY: case NFS4ERR_DENIED: case NFS4ERR_LOCKED: case NFS4ERR_GRACE: return B_WOULD_BLOCK; case NFS4ERR_STALE: case NFS4ERR_FHEXPIRED: return B_ENTRY_NOT_FOUND; // ... default: return B_ERROR; } }