root/src/add-ons/kernel/file_systems/nfs4/InodeRegular.cpp
/*
 * Copyright 2012 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Paweł Dziepak, pdziepak@quarnos.org
 */


#include "Inode.h"

#include <string.h>

#include <AutoDeleter.h>
#include <fs_cache.h>
#include <NodeMonitor.h>

#include "IdMap.h"
#include "Request.h"
#include "RootInode.h"


status_t
Inode::CreateState(const char* name, int mode, int perms, OpenState* state,
        OpenDelegationData* delegationData) {
        ASSERT(name != NULL);
        ASSERT(state != NULL);
        ASSERT(delegationData != NULL);

        uint64 fileID;
        FileHandle handle;
        ChangeInfo changeInfo;

        status_t result = CreateFile(name, mode, perms, state, &changeInfo,
                &fileID, &handle, delegationData);
        if (result != B_OK)
                return result;

        FileInfo fileInfo;
        fileInfo.fFileId = fileID;
        fileInfo.fHandle = handle;

        fFileSystem->EnsureNoCollision(FileIdToInoT(fileID), handle);

        fFileSystem->InoIdMap()->AddName(fileInfo, fInfo.fNames, name,
                FileIdToInoT(fileID));

        fCache->Lock();
        if (fCache->Valid()) {
                if (changeInfo.fAtomic
                        && fCache->ChangeInfo() == changeInfo.fBefore) {
                        fCache->AddEntry(name, fileID, true);
                        fCache->SetChangeInfo(changeInfo.fAfter);
                } else
                        fCache->Trash();
        }
        fCache->Unlock();

        state->fFileSystem = fFileSystem;
        state->fInfo = fileInfo;
        state->fMode = mode & O_RWMASK;

        return B_OK;
}


status_t
Inode::Create(const char* name, int mode, int perms, OpenFileCookie* cookie,
        OpenDelegationData* data, ino_t* id)
{
        ASSERT(name != NULL);
        ASSERT(cookie != NULL);
        ASSERT(data != NULL);

        cookie->fMode = mode;
        cookie->fLocks = NULL;

        OpenState* state = new(std::nothrow) OpenState;
        if (state == NULL)
                return B_NO_MEMORY;

        status_t result = CreateState(name, mode, perms, state, data);
        if (result != B_OK) {
                delete state;
                return result;
        }

        cookie->fOpenState = state;

        *id = FileIdToInoT(state->fInfo.fFileId);

        fFileSystem->AddOpenFile(state);
        fFileSystem->Root()->MakeInfoInvalid();

        notify_entry_created(fFileSystem->DevId(), ID(), name, *id);

        return B_OK;
}


status_t
Inode::Open(int mode, OpenFileCookie* cookie)
{
        ASSERT(cookie != NULL);

        MutexLocker locker(fStateLock);

        OpenDelegationData data;
        data.fType = OPEN_DELEGATE_NONE;
        if (fOpenState == NULL) {
                OpenState* state = new(std::nothrow) OpenState;
                if (state == NULL)
                        return B_NO_MEMORY;

                state->fInfo = fInfo;
                state->fFileSystem = fFileSystem;
                state->fMode = mode & O_RWMASK;
                state->fDelegation = fDelegation;
                status_t result = OpenFile(state, mode, &data);
                if (result != B_OK) {
                        delete state;
                        return result;
                }

                fFileSystem->AddOpenFile(state);
                fOpenState = state;
                cookie->fOpenState = state;
                locker.Unlock();
                if (fDelegation == NULL)
                        RevalidateFileCache();
        } else {
                fOpenState->AcquireReference();
                cookie->fOpenState = fOpenState;
                locker.Unlock();

                int newMode = mode & O_RWMASK;
                int oldMode = fOpenState->fMode & O_RWMASK;
                if (oldMode != newMode && oldMode != O_RDWR) {
                        if (oldMode == O_RDONLY)
                                RecallReadDelegation();

                        status_t result = OpenFile(fOpenState, newMode, &data);
                        if (result != B_OK) {
                                locker.Lock();
                                ReleaseOpenState();
                                return result;
                        }
                        fOpenState->fMode = O_RDWR;

                        if (oldMode == O_RDONLY)
                                RevalidateFileCache();
                } else {
                        int newMode = mode & O_RWMASK;
                        uint32 allowed = 0;
                        if (newMode == O_RDWR || newMode == O_RDONLY)
                                allowed |= R_OK;
                        if (newMode == O_RDWR || newMode == O_WRONLY)
                                allowed |= W_OK;

                        status_t result = Access(allowed);
                        if (result != B_OK) {
                                locker.Lock();
                                ReleaseOpenState();
                                return result;
                        }
                }
        }

        if ((mode & O_TRUNC) == O_TRUNC) {
                struct stat st;
                st.st_size = 0;
                WriteStat(&st, B_STAT_SIZE);
                file_cache_set_size(fFileCache, 0);
        }

        cookie->fMode = mode;
        cookie->fLocks = NULL;

        if (data.fType != OPEN_DELEGATE_NONE) {
                Delegation* delegation
                        = new(std::nothrow) Delegation(data, this, fOpenState->fClientID);
                if (delegation != NULL) {
                        delegation->fInfo = fOpenState->fInfo;
                        delegation->fFileSystem = fFileSystem;
                        SetDelegation(delegation);
                }
        }

        return B_OK;
}


status_t
Inode::Close(OpenFileCookie* cookie)
{
        ASSERT(cookie != NULL);
        ASSERT(fOpenState == cookie->fOpenState);

        int mode = cookie->fMode & O_RWMASK;
        if (mode == O_RDWR || mode == O_WRONLY)
                SyncAndCommit(false, cookie);

        MutexLocker _(fStateLock);
        ReleaseOpenState();

        return B_OK;
}


char*
Inode::AttrToFileName(const char* path)
{
        ASSERT(path != NULL);

        char* name = strdup(path);
        if (name == NULL)
                return NULL;

        char* current = strpbrk(name, "/:");
        while (current != NULL) {
                switch (*current) {
                        case '/':
                                *current = '#';
                                break;
                        case ':':
                                *current = '$';
                                break;
                }
                current = strpbrk(name, "/:");
        }

        return name;
}


status_t
Inode::OpenAttr(const char* _name, int mode, OpenAttrCookie* cookie,
        bool create, int32 type)
{
        ASSERT(_name != NULL);
        ASSERT(cookie != NULL);

        (void)type;

        status_t result = LoadAttrDirHandle();
        if (result != B_OK)
                return result;

        char* name = AttrToFileName(_name);
        if (name == NULL)
                return B_NO_MEMORY;
        MemoryDeleter nameDeleter(name);

        OpenDelegationData data;
        data.fType = OPEN_DELEGATE_NONE;

        OpenState* state = new OpenState;
        if (state == NULL)
                return B_NO_MEMORY;

        state->fFileSystem = fFileSystem;
        result = NFS4Inode::OpenAttr(state, name, mode, &data, create);
        if (result != B_OK) {
                delete state;
                return result;
        }

        fFileSystem->AddOpenFile(state);

        cookie->fOpenState = state;
        cookie->fMode = mode;

        if (data.fType != OPEN_DELEGATE_NONE) {
                Delegation* delegation
                        = new(std::nothrow) Delegation(data, this, state->fClientID, true);
                if (delegation != NULL) {
                        delegation->fInfo = state->fInfo;
                        delegation->fFileSystem = fFileSystem;
                        state->fDelegation = delegation;
                        fFileSystem->AddDelegation(delegation);
                }
        }

        if (create || (mode & O_TRUNC) == O_TRUNC) {
                struct stat st;
                st.st_size = 0;
                WriteStat(&st, B_STAT_SIZE, cookie);
        }

        return B_OK;
}


status_t
Inode::CloseAttr(OpenAttrCookie* cookie)
{
        ASSERT(cookie != NULL);

        if (cookie->fOpenState->fDelegation != NULL) {
                cookie->fOpenState->fDelegation->GiveUp();
                fFileSystem->RemoveDelegation(cookie->fOpenState->fDelegation);
        }

        delete cookie->fOpenState->fDelegation;
        delete cookie->fOpenState;
        return B_OK;
}


status_t
Inode::ReadDirect(OpenStateCookie* cookie, off_t pos, void* buffer,
        size_t* _length, bool* eof)
{
        ASSERT(cookie != NULL || fOpenState != NULL);
        ASSERT(buffer != NULL);
        ASSERT(_length != NULL);
        ASSERT(eof != NULL);

        *eof = false;
        uint32 size = 0;

        uint32 ioSize = fFileSystem->Root()->IOSize();
        *_length = min_c(ioSize, *_length);

        status_t result;
        OpenState* state = cookie != NULL ? cookie->fOpenState : fOpenState;
        ReadLocker delegationLocker(fDelegationLock);
        ASSERT(state->fDelegation == fDelegation);
        while (size < *_length && !*eof) {
                uint32 len = *_length - size;
                result = ReadFile(cookie, state, pos + size, &len,
                        reinterpret_cast<char*>(buffer) + size, eof);
                if (result != B_OK) {
                        if (size == 0)
                                return result;
                        else
                                break;
                }

                size += len;
        }

        *_length = size;

        return B_OK;
}


status_t
Inode::Read(OpenFileCookie* cookie, off_t pos, void* buffer, size_t* _length)
{
        ASSERT(cookie != NULL);
        ASSERT(buffer != NULL);
        ASSERT(_length != NULL);

        bool eof = false;
        if ((cookie->fMode & O_NOCACHE) != 0)
                return ReadDirect(cookie, pos, buffer, _length, &eof);

        MutexLocker _(fFileCacheLock);
        return file_cache_read(fFileCache, cookie, pos, buffer, _length);
}


status_t
Inode::WriteDirect(OpenStateCookie* cookie, off_t pos, const void* _buffer,
        size_t* _length)
{
        ASSERT_WITH_DUMP(cookie != NULL || fOpenState != NULL, this);
        ASSERT(_buffer != NULL);
        ASSERT(_length != NULL);

        uint32 size = 0;
        const char* buffer = reinterpret_cast<const char*>(_buffer);

        uint32 ioSize = fFileSystem->Root()->IOSize();
        *_length = min_c(ioSize, *_length);

        bool attribute = false;
        OpenState* state = fOpenState;
        if (cookie != NULL) {
                attribute = cookie->fOpenState->fInfo.fHandle != fInfo.fHandle;
                state = cookie->fOpenState;
        }

        if (!attribute) {
                ReadLocker _(fWriteLock);
                fWriteDirty = true;
        }

        ReadLocker delegationLocker(fDelegationLock);
        ASSERT(state->fDelegation == fDelegation);
        while (size < *_length) {
                uint32 len = *_length - size;
                status_t result = WriteFile(cookie, state, pos + size, &len,
                        buffer + size, attribute);
                if (result != B_OK) {
                        if (size == 0)
                                return result;
                        else
                                break;
                }

                size += len;
        }

        *_length = size;

        fMetaCache.GrowFile(size + pos);
        fFileSystem->Root()->MakeInfoInvalid();

        return B_OK;
}


status_t
Inode::Write(OpenFileCookie* cookie, off_t pos, const void* _buffer,
        size_t* _length)
{
        ASSERT(cookie != NULL);
        ASSERT(_buffer != NULL);
        ASSERT(_length != NULL);

        if (pos < 0)
                pos = 0;

        if ((cookie->fMode & O_RWMASK) == O_RDONLY)
                return B_NOT_ALLOWED;

        if ((cookie->fMode & O_APPEND) != 0)
                pos = fMaxFileSize;

        uint64 fileSize = pos + *_length;
        if (fileSize > fMaxFileSize) {
                status_t result = file_cache_set_size(fFileCache, fileSize);
                if (result != B_OK)
                        return result;
                if (static_cast<uint64>(pos) > ROUNDUP(fMaxFileSize, B_PAGE_SIZE)
                        && (cookie->fMode & O_NOCACHE) == 0) {
                        // Zero out the start of the file cache page where the write begins.
                        // We will let the server zero out the rest of the hole.
                        fMaxFileSize = pos;
                        size_t pageOffset = pos % B_PAGE_SIZE;
                        if (pageOffset != 0)
                                file_cache_write(fFileCache, cookie, pos - pageOffset, NULL, &pageOffset);
                }
                fMaxFileSize = fileSize;
                fMetaCache.GrowFile(fMaxFileSize);
        }

        if ((cookie->fMode & O_NOCACHE) != 0) {
                WriteDirect(cookie, pos, _buffer, _length);
                Commit(cookie->fUid, cookie->fGid);
        }

        return file_cache_write(fFileCache, cookie, pos, _buffer, _length);
}


status_t
Inode::Commit(uid_t uid, gid_t gid)
{
        if (!fWriteDirty)
                return B_OK;

        WriteLocker _(fWriteLock);
        status_t result = CommitWrites(fDelegation != NULL, uid, gid);
        if (result != B_OK)
                return result;
        fWriteDirty = false;
        return B_OK;
}