root/src/add-ons/kernel/file_systems/ufs2/Inode.cpp
/*
 * Copyright 2020 Suhel Mehta, mehtasuhel@gmail.com
 * Copyright 2008-2010, Axel Dörfler, axeld@pinc-software.de.
 * All rights reserved. Distributed under the terms of the MIT License.
 */

#include "Inode.h"
#include <string.h>


#define TRACE_UFS2
#ifdef TRACE_UFS2
#define TRACE(x...) dprintf("\33[34mufs2:\33[0m " x)
#else
#define TRACE(x...) ;
#endif
#define ERROR(x...) dprintf("\33[34mufs2:\33[0m " x)


Inode::Inode(Volume* volume, ino_t id)
        :
        fVolume(volume),
        fID(id),
        fCache(NULL),
        fMap(NULL)
{
        rw_lock_init(&fLock, "ufs2 inode");

        int fd = fVolume->Device();
        ufs2_super_block* superblock = (ufs2_super_block* )&fVolume->SuperBlock();
        int64_t fs_block = ino_to_fsba(superblock, id);
        int64_t offset_in_block = ino_to_fsbo(superblock, id);
        int64_t offset = fs_block * superblock->fs_fsize + offset_in_block * sizeof(fNode);

        if (read_pos(fd, offset, (void*)&fNode, sizeof(fNode)) != sizeof(fNode)) {
                ERROR("Inode::Inode(): IO Error\n");
                fInitStatus = B_IO_ERROR;
                return;
        }
        fInitStatus = B_OK;

        if (fInitStatus == B_OK) {
                if (!IsDirectory() && !IsSymLink()) {
                        fCache = file_cache_create(fVolume->ID(), ID(), Size());
                        fMap = file_map_create(fVolume->ID(), ID(), Size());
                }
        }
}


Inode::Inode(Volume* volume, ino_t id, const ufs2_inode& item)
        :
        fVolume(volume),
        fID(id),
        fCache(NULL),
        fMap(NULL),
        fInitStatus(B_OK),
        fNode(item)
{
        if (!IsDirectory() && !IsSymLink()) {
                fCache = file_cache_create(fVolume->ID(), ID(), Size());
                fMap = file_map_create(fVolume->ID(), ID(), Size());
        }
}


Inode::Inode(Volume* volume)
        :
        fVolume(volume),
        fID(0),
        fCache(NULL),
        fMap(NULL),
        fInitStatus(B_NO_INIT)
{
        rw_lock_init(&fLock, "ufs2 inode");
}


Inode::~Inode()
{
        TRACE("Inode destructor\n");
        file_cache_delete(FileCache());
        file_map_delete(Map());
        TRACE("Inode destructor: Done\n");
}


status_t
Inode::InitCheck()
{
        return fInitStatus;
}


status_t
Inode::ReadAt(off_t file_offset, uint8* buffer, size_t* _length)
{
        int fd = fVolume->Device();
        ufs2_super_block super_block = fVolume->SuperBlock();
        int32_t blockSize = super_block.fs_bsize;
        off_t pos;
        int64_t size = Size();
        off_t startBlockNumber = file_offset / blockSize;
        off_t endBlockNumber = (file_offset + *_length) / blockSize;
        off_t blockOffset = file_offset % blockSize;
        ssize_t length = 0;
        if (size <= file_offset || size < 0 || file_offset < 0) {
                *_length = 0;
                return B_OK;
        }
        if ((int64_t) *_length > size - file_offset)
                *_length = size - file_offset;
        if (startBlockNumber != endBlockNumber) {
                ssize_t remainingLength = blockSize - blockOffset;
                for (; startBlockNumber <= endBlockNumber; startBlockNumber++) {
                        //code for reading multiple blocks
                        pos = FindBlock(startBlockNumber, blockOffset);
                        if (remainingLength > (int64_t) *_length - length)
                                remainingLength = *_length - length;
                        length += read_pos(fd, pos, buffer + length, remainingLength);
                        blockOffset = 0;
                        remainingLength = *_length - length;
                        remainingLength = blockSize;
                }
                *_length = length;
                return B_OK;
        }
        pos = FindBlock(startBlockNumber, blockOffset);
        length = read_pos(fd, pos, buffer, *_length);
        *_length = length;
        return B_OK;

}


off_t
Inode::FindBlock(off_t blockNumber, off_t blockOffset)
{
        int fd = fVolume->Device();
        ufs2_super_block super_block = fVolume->SuperBlock();
        int32_t blockSize = super_block.fs_bsize;
        int32_t fragmentSize = super_block.fs_fsize;
        off_t indirectOffset;
        int64_t directBlock;
        off_t numberOfBlockPointers = blockSize / 8;
        const off_t numberOfIndirectBlocks = numberOfBlockPointers;
        const off_t numberOfDoubleIndirectBlocks = numberOfBlockPointers
                * numberOfBlockPointers;
        if (blockNumber < 12) {
                // read from direct block
                return GetBlockPointer(blockNumber) * fragmentSize + blockOffset;

        } else if (blockNumber < numberOfIndirectBlocks + 12) {
                //read from indirect block
                blockNumber = blockNumber - 12;
                indirectOffset = GetIndirectBlockPointer() *
                        fragmentSize + (8 * blockNumber);
                read_pos(fd, indirectOffset,
                        (void*)&directBlock, sizeof(directBlock));

                return directBlock * fragmentSize + blockOffset;

        } else if (blockNumber < numberOfDoubleIndirectBlocks
                        + numberOfIndirectBlocks + 12) {
                // Data is in double indirect block
                // Subract the already read blocks
                blockNumber = blockNumber - numberOfBlockPointers - 12;
                // Calculate indirect block inside double indirect block
                off_t indirectBlockNumber = blockNumber / numberOfBlockPointers;
                indirectOffset = GetDoubleIndirectBlockPtr() *
                        fragmentSize + (8 * indirectBlockNumber);

                int64_t indirectPointer;
                read_pos(fd, indirectOffset,
                        (void*)&indirectPointer, sizeof(directBlock));

                indirectOffset = indirectPointer * fragmentSize
                        + (8 * (blockNumber % numberOfBlockPointers));

                read_pos(fd, indirectOffset,
                        (void*)&directBlock, sizeof(directBlock));

                return directBlock * fragmentSize + blockOffset;

        } else if (blockNumber < (numberOfIndirectBlocks
                                * numberOfDoubleIndirectBlocks)
                                + (numberOfDoubleIndirectBlocks + numberOfBlockPointers + 12)) {
                // Reading from triple indirect block
                blockNumber = blockNumber - numberOfDoubleIndirectBlocks
                        - numberOfBlockPointers - 12;

                // Get double indirect block
                // Double indirect block no
                off_t indirectBlockNumber = blockNumber / numberOfDoubleIndirectBlocks;

                // offset to double indirect block ptr
                indirectOffset = GetTripleIndirectBlockPtr() *
                        fragmentSize + (8 * indirectBlockNumber);

                int64_t indirectPointer;
                // Get the double indirect block ptr
                read_pos(fd, indirectOffset,
                        (void*)&indirectPointer, sizeof(directBlock));

                // Get the indirect block
                // number of indirect block ptr
                indirectBlockNumber = blockNumber / numberOfBlockPointers;
                // Indirect block ptr offset
                indirectOffset = indirectPointer * fragmentSize
                        + (8 * indirectBlockNumber);

                read_pos(fd, indirectOffset,
                        (void*)&indirectPointer, sizeof(directBlock));

                // Get direct block pointer
                indirectOffset = indirectPointer * fragmentSize
                        + (8 * (blockNumber % numberOfBlockPointers));

                read_pos(fd, indirectOffset,
                        (void*)&directBlock, sizeof(directBlock));

                return directBlock * fragmentSize + blockOffset;
        }

        return B_BAD_VALUE;
}


status_t
Inode::ReadLink(char* buffer, size_t *_bufferSize)
{
        strlcpy(buffer, fNode.symlinkpath, *_bufferSize);
        return B_OK;
}


status_t
Inode::CheckPermissions(int accessMode) const
{
        // you never have write access to a read-only volume
        if ((accessMode & W_OK) != 0/* && fVolume->IsReadOnly()*/)
                return B_READ_ONLY_DEVICE;

        return check_access_permissions(accessMode, Mode(), (gid_t)GroupID(),
                (uid_t)UserID());
}