root/src/add-ons/kernel/file_systems/bfs/Query.cpp
/*
 * Copyright 2001-2020, Axel Dörfler, axeld@pinc-software.de.
 * Copyright 2010, Clemens Zeidler <haiku@clemens-zeidler.de>
 * Copyright 2024, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 */

#include "Query.h"

#include "BPlusTree.h"
#include "bfs.h"
#include "Debug.h"
#include "Index.h"
#include "Inode.h"
#include "Volume.h"

#include <file_systems/QueryParser.h>


// #pragma mark - QueryPolicy


struct Query::QueryPolicy {
        typedef Query Context;
        typedef ::Inode Entry;
        typedef ::Inode Node;

        struct Index : ::Index {
                bool isSpecialTime;

                Index(Context* context)
                        :
                        ::Index(context->fVolume),
                        isSpecialTime(false)
                {
                }
        };

        struct IndexIterator : TreeIterator {
                off_t offset;
                bool isSpecialTime;

                IndexIterator(BPlusTree* tree)
                        :
                        TreeIterator(tree),
                        offset(0),
                        isSpecialTime(false)
                {
                }
        };

        struct NodeHolder {
                Vnode vnode;
                NodeGetter nodeGetter;
                RecursiveLocker smallDataLocker;
        };

        static const int32 kMaxFileNameLength = INODE_FILE_NAME_LENGTH;

        // Entry interface

        static ino_t EntryGetParentID(Inode* inode)
        {
                return inode->ParentID();
        }

        static Node* EntryGetNode(Entry* entry)
        {
                return entry;
        }

        static ino_t EntryGetNodeID(Entry* entry)
        {
                return entry->ID();
        }

        static ssize_t EntryGetName(Inode* inode, void* buffer, size_t bufferSize)
        {
                status_t status = inode->GetName((char*)buffer, bufferSize);
                if (status != B_OK)
                        return status;
                return strlen((const char*)buffer) + 1;
        }

        static const char* EntryGetNameNoCopy(NodeHolder& holder, Inode* inode)
        {
                // we need to lock before accessing Inode::Name()
                status_t status = holder.nodeGetter.SetTo(inode);
                if (status != B_OK)
                        return NULL;

                holder.smallDataLocker.SetTo(inode->SmallDataLock(), false);
                uint8* buffer = (uint8*)inode->Name(holder.nodeGetter.Node());
                if (buffer == NULL) {
                        holder.smallDataLocker.Unlock();
                        return NULL;
                }

                return (const char*)buffer;
        }

        // Index interface

        static status_t IndexSetTo(Index& index, const char* attribute)
        {
                status_t status = index.SetTo(attribute);
                if (status == B_OK) {
                        // The special time flag is set if the time values are shifted
                        // 64-bit values to reduce the number of duplicates.
                        // We have to be able to compare them against unshifted values
                        // later. The only index which needs this is the last_modified
                        // index, but we may want to open that feature for other indices,
                        // too one day.
                        index.isSpecialTime = (strcmp(attribute, "last_modified") == 0);
                }
                return status;
        }

        static void IndexUnset(Index& index)
        {
                index.Unset();
        }

        static int32 IndexGetSize(Index& index)
        {
                off_t size = index.Node()->Size() / index.Node()->GetVolume()->BlockSize();
                if (size > INT32_MAX)
                        return INT32_MAX;
                return size;
        }

        static type_code IndexGetType(Index& index)
        {
                return index.Type();
        }

        static int32 IndexGetKeySize(Index& index)
        {
                return index.KeySize();
        }

        static IndexIterator* IndexCreateIterator(Index& index)
        {
                IndexIterator* iterator = new(std::nothrow) IndexIterator(index.Node()->Tree());
                if (iterator == NULL)
                        return NULL;

                iterator->isSpecialTime = index.isSpecialTime;
                return iterator;
        }

        // IndexIterator interface

        static void IndexIteratorDelete(IndexIterator* iterator)
        {
                delete iterator;
        }

        static status_t IndexIteratorFind(IndexIterator* iterator,
                const void* value, size_t size)
        {
                int64 shiftedTime;
                if (iterator->isSpecialTime) {
                        // int64 time index; convert value.
                        shiftedTime = *(int64*)value << INODE_TIME_SHIFT;
                        value = &shiftedTime;
                }

                return iterator->Find((const uint8*)value, size);
        }

        static status_t IndexIteratorFetchNextEntry(IndexIterator* iterator,
                void* indexValue, size_t* _keyLength, size_t bufferSize, size_t* _duplicate)
        {
                uint16 keyLength;
                uint16 duplicate;
                status_t status = iterator->GetNextEntry((uint8*)indexValue, &keyLength,
                        bufferSize, &iterator->offset, &duplicate);
                if (status != B_OK)
                        return status;

                if (iterator->isSpecialTime) {
                        // int64 time index; convert value.
                        *(int64*)indexValue >>= INODE_TIME_SHIFT;
                }

                *_keyLength = keyLength;
                *_duplicate = duplicate;
                return B_OK;
        }

        static status_t IndexIteratorGetEntry(Context* context, IndexIterator* iterator,
                NodeHolder& holder, Inode** _entry)
        {
                holder.vnode.SetTo(context->fVolume, iterator->offset);
                Inode* inode;
                status_t status = holder.vnode.Get(&inode);
                if (status != B_OK) {
                        REPORT_ERROR(status);
                        FATAL(("could not get inode %" B_PRIdOFF " in index!\n", iterator->offset));
                        return status;
                }

                *_entry = inode;
                return B_OK;
        }

        static void IndexIteratorSkipDuplicates(IndexIterator* iterator)
        {
                iterator->SkipDuplicates();
        }

        static void IndexIteratorSuspend(IndexIterator* indexIterator)
        {
                // Nothing to do.
        }

        static void IndexIteratorResume(IndexIterator* indexIterator)
        {
                // Nothing to do.
        }

        // Node interface

        static const off_t NodeGetSize(Inode* inode)
        {
                return inode->Size();
        }

        static time_t NodeGetLastModifiedTime(Inode* inode)
        {
                return bfs_inode::ToSecs(inode->Node().LastModifiedTime());
        }

        static status_t NodeGetAttribute(NodeHolder& holder, Inode* inode,
                const char* attributeName, uint8*& buffer, size_t* size, int32* type)
        {
                status_t status = holder.nodeGetter.SetTo(inode);
                if (status != B_OK)
                        return status;

                Inode* attribute;

                holder.smallDataLocker.SetTo(inode->SmallDataLock(), false);
                small_data* smallData = inode->FindSmallData(holder.nodeGetter.Node(),
                        attributeName);
                if (smallData != NULL) {
                        buffer = smallData->Data();
                        *type = smallData->type;
                        *size = smallData->data_size;
                } else {
                        // needed to unlock the small_data section as fast as possible
                        holder.smallDataLocker.Unlock();
                        holder.nodeGetter.Unset();

                        if (inode->GetAttribute(attributeName, &attribute) == B_OK) {
                                *type = attribute->Type();
                                if (*size > (size_t)attribute->Size())
                                        *size = attribute->Size();

                                if (*size > MAX_INDEX_KEY_LENGTH)
                                        *size = MAX_INDEX_KEY_LENGTH;

                                if (attribute->ReadAt(0, (uint8*)buffer, size) < B_OK) {
                                        inode->ReleaseAttribute(attribute);
                                        return B_IO_ERROR;
                                }
                                inode->ReleaseAttribute(attribute);
                        } else
                                return B_ENTRY_NOT_FOUND;
                }
                return B_OK;
        }

        static Entry* NodeGetFirstReferrer(Node* node)
        {
                return node;
        }

        static Entry* NodeGetNextReferrer(Node* node, Entry* entry)
        {
                return NULL;
        }

        static bool NodeIsDeleted(Node* node)
        {
                return node->IsDeleted();
        }

        // Volume interface

        static dev_t ContextGetVolumeID(Context* context)
        {
                return context->fVolume->ID();
        }
};


// #pragma mark - Query


Query::Query(Volume* volume)
        :
        fVolume(volume),
        fImpl(NULL)
{
}


Query::~Query()
{
        if (fImpl != NULL) {
                if ((fImpl->Flags() & B_LIVE_QUERY) != 0)
                        fVolume->RemoveQuery(this);

                delete fImpl;
        }
}


/*static*/ status_t
Query::Create(Volume* volume, const char* queryString, uint32 flags,
        port_id port, uint32 token, Query*& _query)
{
        Query* query = new(std::nothrow) Query(volume);
        if (query == NULL)
                return B_NO_MEMORY;

        status_t error = query->_Init(queryString, flags, port, token);
        if (error != B_OK) {
                delete query;
                return error;
        }

        _query = query;
        return B_OK;
}


status_t
Query::Rewind()
{
        return fImpl->Rewind();
}


status_t
Query::GetNextEntry(struct dirent* entry, size_t size)
{
        return fImpl->GetNextEntry(entry, size);
}


void
Query::LiveUpdate(Inode* inode, const char* attribute, int32 type,
        const void* oldKey, size_t oldLength, const void* newKey, size_t newLength)
{
        fImpl->LiveUpdate(inode, inode, attribute, type,
                (const uint8*)oldKey, oldLength,
                (const uint8*)newKey, newLength);
}


void
Query::LiveUpdateRenameMove(Inode* inode,
        ino_t oldDirectoryID, const char* oldName, size_t oldLength,
        ino_t newDirectoryID, const char* newName, size_t newLength)
{
        fImpl->LiveUpdateRenameMove(inode, inode,
                oldDirectoryID, oldName, oldLength,
                newDirectoryID, newName, newLength);
}


status_t
Query::_Init(const char* queryString, uint32 flags, port_id port, uint32 token)
{
        status_t error = QueryImpl::Create(this, queryString, flags, port, token,
                fImpl);
        if (error != B_OK)
                return error;

        if ((fImpl->Flags() & B_LIVE_QUERY) != 0)
                fVolume->AddQuery(this);

        return B_OK;
}