root/src/add-ons/kernel/file_systems/nfs4/ReplyInterpreter.cpp
/*
 * 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 <string.h>

#include <AutoDeleter.h>
#include <util/kernel_cpp.h>

#include "Cookie.h"


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<char*>(fRootPath[i]));
        }
        delete[] fRootPath;
        
        for (uint32 i = 0; i < fCount; i++)
                free(const_cast<char*>(fLocations[i]));
        delete[] fLocations;
}


FSLocations::~FSLocations()
{
        CALLED();

        if (fRootPath != NULL) {
                for (uint32 i = 0; fRootPath[i] != NULL; i++)
                        free(const_cast<char*>(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<char*>(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<LockType>(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<const char**>(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<uint32> _(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<void*>(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<time_t>(stream.GetHyper());
                ts.tv_nsec = static_cast<long>(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<time_t>(stream.GetHyper());
                ts.tv_nsec = static_cast<long>(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<time_t>(stream.GetHyper());
                ts.tv_nsec = static_cast<long>(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<time_t>(stream.GetHyper());
                ts.tv_nsec = static_cast<long>(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) {
#if DEBUG
                ERROR("NFS Error: %s\n", strerror(result));
#else
                // Lookup failures are routine. Reporting them could obscure more important error messages.
                if (op != OpLookUp || result != B_ENTRY_NOT_FOUND)
                        ERROR("NFS Error: %s\n", strerror(result));
#endif
                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;
        }
}