root/src/add-ons/kernel/file_systems/btrfs/DirectoryIterator.cpp
/*
 * Copyright 2017, Chế Vũ Gia Hy, cvghy116@gmail.com.
 * Copyright 2011-2013, Jérôme Duval, korli@users.berlios.de.
 * This file may be used under the terms of the MIT License.
 */


#include "DirectoryIterator.h"
#include "CRCTable.h"


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


DirectoryIterator::DirectoryIterator(Inode* inode)
        :
        fOffset(0),
        fInode(inode),
        fIterator(NULL)
{
        btrfs_key key;
        key.SetType(BTRFS_KEY_TYPE_DIR_INDEX);
        key.SetObjectID(inode->ID());
        key.SetOffset(BTREE_BEGIN);
        fIterator = new(std::nothrow) TreeIterator(inode->GetVolume()->FSTree(),
                key);
}


DirectoryIterator::~DirectoryIterator()
{
        delete fIterator;
        fIterator = NULL;
}


status_t
DirectoryIterator::InitCheck()
{
        return fIterator != NULL ? B_OK : B_NO_MEMORY;
}


status_t
DirectoryIterator::GetNext(char* name, size_t* _nameLength, ino_t* _id)
{
        if (fOffset == 0) {
                if (*_nameLength < 3)
                        return B_BUFFER_OVERFLOW;
                *_nameLength = 2;
                strlcpy(name, "..", *_nameLength + 1);
                *_id = fInode->ID();
                fOffset = 1;
                return B_OK;
        } else if (fOffset == 1) {
                if (*_nameLength < 2)
                        return B_BUFFER_OVERFLOW;
                *_nameLength = 1;
                strlcpy(name, ".", *_nameLength + 1);
                fOffset = 2;
                if (fInode->ID() == BTRFS_FIRST_SUBVOLUME) {
                        *_id = fInode->ID();
                        return B_OK;
                }
                return fInode->FindParent(_id);
        }

        btrfs_dir_entry* entries;
        uint32 entries_length;
        status_t status = fIterator->GetNextEntry((void**)&entries, &entries_length);
        if (status != B_OK)
                return status;

        btrfs_dir_entry* entry = entries;
        uint16 current = 0;
        while (current < entries_length) {
                current += entry->Length();
                break;
                // TODO there could be several entries with the same name hash
                entry = (btrfs_dir_entry*)((uint8*)entry + entry->Length());
        }

        size_t length = entry->NameLength();

        TRACE("DirectoryIterator::GetNext() entries_length %ld name_length %d\n",
                entries_length, entry->NameLength());

        if (length + 1 > *_nameLength) {
                free(entries);
                return B_BUFFER_OVERFLOW;
        }

        memcpy(name, entry->name, length);
        name[length] = '\0';
        *_nameLength = length;
        *_id = entry->InodeID();
        free(entries);

        return B_OK;
}


status_t
DirectoryIterator::Lookup(const char* name, size_t nameLength, ino_t* _id)
{
        if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
                if (strcmp(name, ".") == 0
                        || fInode->ID() == BTRFS_FIRST_SUBVOLUME) {
                        *_id = fInode->ID();
                        return B_OK;
                }
                return fInode->FindParent(_id);
        }

        uint32 hash = calculate_crc((uint32)~1, (uint8*)name, nameLength);
        btrfs_key key;
        key.SetType(BTRFS_KEY_TYPE_DIR_ITEM);
        key.SetObjectID(fInode->ID());
        key.SetOffset(hash);
        BTree::Path path(fInode->GetVolume()->FSTree());

        btrfs_dir_entry* entries;
        uint32 length;
        status_t status = fInode->GetVolume()->FSTree()->FindExact(&path, key,
                (void**)&entries, &length);
        if (status != B_OK) {
                TRACE("DirectoryIterator::Lookup(): Couldn't find entry with hash %" B_PRIu32
                        " \"%s\"\n", hash, name);
                return status;
        }

        btrfs_dir_entry* entry = entries;
        uint16 current = 0;
        while (current < length) {
                if (entry->NameLength() == nameLength
                        && strncmp((char*)entry->name, name, nameLength) == 0) {
                        *_id = entry->InodeID();
                        free(entries);
                        return B_OK;
                }
                current += entry->Length();
                entry = (btrfs_dir_entry*)((uint8*)entry + entry->Length());
        }
        free(entries);
        return B_ENTRY_NOT_FOUND;
}


status_t
DirectoryIterator::Rewind()
{
        fIterator->Rewind();
        fOffset = BTREE_BEGIN;
        return B_OK;
}