root/src/add-ons/kernel/file_systems/xfs/Symlink.cpp
/*
 * Copyright 2022, Raghav Sharma, raghavself28@gmail.com
 * Distributed under the terms of the MIT License.
 */


#include "Symlink.h"

#include "VerifyHeader.h"


Symlink::Symlink(Inode* inode)
        :
        fInode(inode),
        fSymlinkBuffer(NULL)
{
}


Symlink::~Symlink()
{
        delete fSymlinkBuffer;
}


status_t
Symlink::_FillMapEntry()
{
        void* pointerToMap = DIR_DFORK_PTR(fInode->Buffer(), fInode->CoreInodeSize());

        uint64 firstHalf = *((uint64*)pointerToMap);
        uint64 secondHalf = *((uint64*)pointerToMap + 1);
                //dividing the 128 bits into 2 parts.
        firstHalf = B_BENDIAN_TO_HOST_INT64(firstHalf);
        secondHalf = B_BENDIAN_TO_HOST_INT64(secondHalf);
        fMap.br_state = firstHalf >> 63;
        fMap.br_startoff = (firstHalf & MASK(63)) >> 9;
        fMap.br_startblock = ((firstHalf & MASK(9)) << 43) | (secondHalf >> 21);
        fMap.br_blockcount = secondHalf & MASK(21);
        TRACE("Extent::Init: startoff:(%" B_PRIu64 "), startblock:(%" B_PRIu64 "),"
                "blockcount:(%" B_PRIu64 "),state:(%" B_PRIu8 ")\n", fMap.br_startoff, fMap.br_startblock,
                fMap.br_blockcount, fMap.br_state);

        return B_OK;
}


status_t
Symlink::_FillBuffer()
{
        if (fMap.br_state != 0)
                return B_BAD_VALUE;

        int len = fInode->DirBlockSize();
        fSymlinkBuffer = new(std::nothrow) char[len];
        if (fSymlinkBuffer == NULL)
                return B_NO_MEMORY;

        xfs_daddr_t readPos =
                fInode->FileSystemBlockToAddr(fMap.br_startblock);

        if (read_pos(fInode->GetVolume()->Device(), readPos, fSymlinkBuffer, len)
                != len) {
                ERROR("Extent::FillBlockBuffer(): IO Error");
                return B_IO_ERROR;
        }

        return B_OK;
}


status_t
Symlink::_ReadLocalLink(off_t pos, char* buffer, size_t* _length)
{
        // All symlinks contents are inside inode itself

        size_t lengthToRead = fInode->Size();

        if (*_length < lengthToRead)
                lengthToRead = *_length;

        char* offset = (char*)(DIR_DFORK_PTR(fInode->Buffer(), fInode->CoreInodeSize()));

        memcpy(buffer, offset, lengthToRead);

        *_length = lengthToRead;

        return B_OK;

}


status_t
Symlink::_ReadExtentLink(off_t pos, char* buffer, size_t* _length)
{
        status_t status;
        // First fill up extent, then Symlink block buffer
        status = _FillMapEntry();
        if (status != B_OK)
                return status;

        status = _FillBuffer();
        if (status != B_OK)
                return status;

        uint32 offset = 0;
        // If it is Version 5 xfs then we have Symlink header
        if (fInode->Version() == 3) {
                SymlinkHeader* header = (SymlinkHeader*)fSymlinkBuffer;
                if (!VerifyHeader<SymlinkHeader>(header, fSymlinkBuffer, fInode, 0, &fMap, 0)) {
                        ERROR("Invalid data header");
                        return B_BAD_VALUE;
                }
                offset += sizeof(SymlinkHeader);
        }

        size_t lengthToRead = fInode->Size();

        if (*_length < lengthToRead)
                lengthToRead = *_length;

        memcpy(buffer, fSymlinkBuffer + offset, lengthToRead);

        *_length = lengthToRead;

        return B_OK;
}


status_t
Symlink::ReadLink(off_t pos, char* buffer, size_t* _length)
{
        switch (fInode->Format()) {
                case XFS_DINODE_FMT_LOCAL:
                        return _ReadLocalLink(pos, buffer, _length);
                case XFS_DINODE_FMT_EXTENTS:
                        return _ReadExtentLink(pos, buffer, _length);
                default:
                        return B_BAD_VALUE;
        }