root/src/bin/bfs_tools/lib/Inode.cpp
/*
 * Copyright 2001-2025 pinc Software. All Rights Reserved.
 * Released under the terms of the MIT license.
 */

//!     BFS Inode classes


#include "Inode.h"
#include "BPlusTree.h"

#include <Directory.h>
#include <SymLink.h>
#include <Entry.h>
#include <Path.h>
#include <String.h>

#include <new>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>


class NodeGetter {
        public:
                NodeGetter(Inode* inode)
                        :
                        fInode(inode)
                {
                        fInode->AcquireBuffer();
                }

                ~NodeGetter()
                {
                        fInode->ReleaseBuffer();
                }

        private:
                Inode*  fInode;
};


//      #pragma mark -


Inode::Inode(Disk* disk, bfs_inode* inode, bool ownBuffer)
        :
        fDisk(disk),
        fInode(inode),
        fOwnBuffer(ownBuffer),
        fPath(NULL),
        fRefCount(1),
        fCurrentSmallData(NULL),
        fAttributes(NULL),
        fAttributeBuffer(NULL)
{
        if (inode != NULL)
                fBlockRun = inode->inode_num;
}


Inode::Inode(const Inode& inode)
        :
        fDisk(inode.fDisk),
        fInode(inode.fInode),
        fOwnBuffer(false),
        fPath(NULL),
        fBlockRun(inode.fBlockRun),
        fRefCount(1),
        fCurrentSmallData(NULL),
        fAttributes(NULL),
        fAttributeBuffer(NULL)
{
}


Inode::~Inode()
{
        _Unset();
}


void
Inode::_Unset()
{
        if (fOwnBuffer)
                free(fInode);

        fInode = NULL;
        fBlockRun.SetTo(0, 0, 0);

        free(fPath);
        fPath = NULL;

        delete fAttributes;
        fAttributes = NULL;
}


status_t
Inode::SetTo(bfs_inode *inode)
{
        _Unset();

        fInode = inode;
        fBlockRun = inode->inode_num;
        return B_OK;
}


status_t
Inode::InitCheck() const
{
        if (!fInode)
                return B_ERROR;

        // test inode magic and flags
        if (fInode->magic1 != INODE_MAGIC1
                || !(fInode->flags & INODE_IN_USE)
                || fInode->inode_num.length != 1)
                return B_ERROR;

        if (fDisk->BlockSize()) {
                        // matches known block size?
                if (fInode->inode_size != fDisk->SuperBlock()->inode_size
                        // parent resides on disk?
                        || fInode->parent.allocation_group > fDisk->SuperBlock()->num_ags
                        || fInode->parent.allocation_group < 0
                        || fInode->parent.start > (1L << fDisk->SuperBlock()->ag_shift)
                        || fInode->parent.length != 1
                        // attributes, too?
                        || fInode->attributes.allocation_group > fDisk->SuperBlock()->num_ags
                        || fInode->attributes.allocation_group < 0
                        || fInode->attributes.start > (1L << fDisk->SuperBlock()->ag_shift))
                        return B_ERROR;
        } else {
                // is inode size one of the valid values?
                switch (fInode->inode_size) {
                        case 1024:
                        case 2048:
                        case 4096:
                        case 8192:
                                break;
                        default:
                                return B_ERROR;
                }
        }
        return B_OK;
        // is inode on a boundary matching it's size?
        //return (Offset() % fInode->inode_size) == 0 ? B_OK : B_ERROR;
}


status_t
Inode::CopyBuffer()
{
        if (!fInode)
                return B_ERROR;

        bfs_inode *buffer = (bfs_inode *)malloc(fInode->inode_size);
        if (!buffer)
                return B_NO_MEMORY;

        memcpy(buffer, fInode, fInode->inode_size);
        fInode = buffer;
        fOwnBuffer = true;
        BufferClobbered();
                // this must not be deleted anymore

        return B_OK;
}


/*static*/ bool
Inode::_LowMemory()
{
#ifdef __HAIKU__
        static bigtime_t lastChecked;
        static int32 percentUsed;

        if (system_time() > lastChecked + 1000000LL) {
                system_info info;
                get_system_info(&info);
                percentUsed = 100 * info.used_pages / info.max_pages;
        }

        return percentUsed > 75;
#else
        return false;
#endif
}


void
Inode::ReleaseBuffer()
{
        if (atomic_add(&fRefCount, -1) != 1)
                return;

        if (fOwnBuffer) {
                if (!_LowMemory())
                        return;

                free(fInode);
                fInode = NULL;
        }
}


status_t
Inode::AcquireBuffer()
{
        if (atomic_add(&fRefCount, 1) != 0)
                return B_OK;

        if (!fOwnBuffer || fInode != NULL)
                return B_OK;

        fInode = (bfs_inode*)malloc(fDisk->BlockSize());
        if (fInode == NULL)
                return B_NO_MEMORY;

        ssize_t bytesRead = fDisk->ReadAt(Offset(), fInode, fDisk->BlockSize());
        if (bytesRead < B_OK)
                return bytesRead;

        return B_OK;
}


void
Inode::BufferClobbered()
{
        AcquireBuffer();
}


void
Inode::SetParent(const block_run& run)
{
        fInode->parent = run;
        BufferClobbered();
}


void
Inode::SetBlockRun(const block_run& run)
{
        fInode->inode_num = run;
        fBlockRun = run;
        BufferClobbered();
}


void
Inode::SetMode(uint32 mode)
{
        fInode->mode = mode;
        BufferClobbered();
}


status_t
Inode::SetName(const char *name)
{
        if (name == NULL || *name == '\0')
                return B_BAD_VALUE;

        small_data *data = fInode->small_data_start, *nameData = NULL;
        BufferClobbered();

        while (!data->IsLast(fInode)) {
                if (data->type == FILE_NAME_TYPE
                        && data->name_size == FILE_NAME_NAME_LENGTH
                        && *data->Name() == FILE_NAME_NAME)
                        nameData = data;

                data = data->Next();
        }

        int32 oldLength = nameData == NULL ? 0 : nameData->data_size;
        int32 newLength = strlen(name) + (nameData == NULL ? sizeof(small_data) + 5 : 0);

        if ((addr_t)data + newLength - oldLength >= (addr_t)(fInode
                        + fDisk->BlockSize()))
                return B_NO_MEMORY;

        if (nameData == NULL) {
                memmove(newLength + (uint8 *)fInode->small_data_start,
                        fInode->small_data_start,
                        (addr_t)data - (addr_t)fInode->small_data_start);
                nameData = fInode->small_data_start;
        } else {
                memmove(newLength + (uint8 *)nameData, nameData,
                        (addr_t)data - (addr_t)fInode->small_data_start);
        }

        memset(nameData, 0, sizeof(small_data) + 5 + strlen(name));
        nameData->type = FILE_NAME_TYPE;
        nameData->name_size = FILE_NAME_NAME_LENGTH;
        nameData->data_size = strlen(name);
        *nameData->Name() = FILE_NAME_NAME;
        strcpy((char *)nameData->Data(),name);

        return B_OK;
}


const char *
Inode::Name() const
{
        if (InitCheck() != B_OK) {
                puts("Not getting name because node is invalid");
                return NULL;
        }
        small_data *data = fInode->small_data_start;
        while (!data->IsLast(fInode)) {
                if (data->type == FILE_NAME_TYPE
                        && data->name_size == FILE_NAME_NAME_LENGTH
                        && *data->Name() == FILE_NAME_NAME)
                        return (const char *)data->Data();

                data = data->Next();
        }
        return NULL;
}


status_t
Inode::GetNextSmallData(small_data **smallData)
{
        if (!fInode)
                return B_ERROR;

        small_data *data = *smallData;

        // begin from the start?
        if (data == NULL)
                data = fInode->small_data_start;
        else
                data = data->Next();

        // is already last item?
        if (data->IsLast(fInode))
                return B_ENTRY_NOT_FOUND;

        *smallData = data;
        return B_OK;
}


status_t
Inode::RewindAttributes()
{
        fCurrentSmallData = NULL;

        if (fAttributes != NULL)
                fAttributes->Rewind();

        return B_OK;
}


status_t
Inode::GetNextAttribute(char *name, uint32 *type, void **data, size_t *length)
{
        // read attributes out of the small data section

        if (fCurrentSmallData == NULL || !fCurrentSmallData->IsLast(fInode)) {
                if (fCurrentSmallData == NULL)
                        fCurrentSmallData = fInode->small_data_start;
                else
                        fCurrentSmallData = fCurrentSmallData->Next();

                // skip name attribute
                if (!fCurrentSmallData->IsLast(fInode)
                        && fCurrentSmallData->name_size == FILE_NAME_NAME_LENGTH
                        && *fCurrentSmallData->Name() == FILE_NAME_NAME)
                        fCurrentSmallData = fCurrentSmallData->Next();

                if (!fCurrentSmallData->IsLast(fInode)) {
                        strncpy(name,fCurrentSmallData->Name(), B_FILE_NAME_LENGTH);
                        *type = fCurrentSmallData->type;
                        *data = fCurrentSmallData->Data();
                        *length = fCurrentSmallData->data_size;

                        return B_OK;
                }
        }

        // read attributes out of the attribute directory

        if (Attributes().IsZero())
                return B_ENTRY_NOT_FOUND;

        if (fAttributes == NULL)
                fAttributes = (Directory *)Inode::Factory(fDisk, Attributes());

        status_t status = fAttributes ? fAttributes->InitCheck() : B_ERROR;
        if (status < B_OK)
                return status;

        block_run run;
        status = fAttributes->GetNextEntry(name, &run);
        if (status < B_OK) {
                free(fAttributeBuffer);
                fAttributeBuffer = NULL;
                return status;
        }

        Attribute *attribute = (Attribute *)Inode::Factory(fDisk, run);
        if (attribute == NULL || attribute->InitCheck() < B_OK)
                return B_IO_ERROR;

        *type = attribute->Type();

        void *buffer = realloc(fAttributeBuffer, attribute->Size());
        if (buffer == NULL) {
                free(fAttributeBuffer);
                fAttributeBuffer = NULL;
                delete attribute;
                return B_NO_MEMORY;
        }
        fAttributeBuffer = buffer;

        ssize_t size =  attribute->Read(fAttributeBuffer, attribute->Size());
        delete attribute;

        *length = size;
        *data = fAttributeBuffer;

        return size < B_OK ? size : B_OK;
}


status_t
Inode::_FindPath(Inode::Source *source)
{
        BString path;

        block_run parent = Parent();
        while (!parent.IsZero() && parent != fDisk->Root()) {
                Inode *inode;
                if (source)
                        inode = source->InodeAt(parent);
                else
                        inode = Inode::Factory(fDisk, parent);

                if (inode == NULL
                        || inode->InitCheck() < B_OK
                        || inode->Name() == NULL
                        || !*inode->Name()) {
                        BString sub;
                        sub << "__recovered " << parent.allocation_group << ":"
                                << (int32)parent.start << "/";
                        path.Prepend(sub);

                        delete inode;
                        break;
                }
                parent = inode->Parent();
                path.Prepend("/");
                path.Prepend(inode->Name());

                delete inode;
        }
        fPath = strdup(path.String());

        return B_OK;
}


const char *
Inode::Path(Inode::Source *source)
{
        if (fPath == NULL)
                _FindPath(source);

        return fPath;
}


status_t
Inode::CopyTo(const char *root, bool fullPath, Inode::Source *source)
{
        if (root == NULL)
                return B_ENTRY_NOT_FOUND;

        BString path;

        if (fullPath)
                path.Append(Path(source));

        if (*(root + strlen(root) - 1) != '/')
                path.Prepend("/");
        path.Prepend(root);

        return create_directory(path.String(), 0777);
}


status_t
Inode::CopyAttributesTo(BNode *node)
{
        // copy attributes

        RewindAttributes();

        char name[B_FILE_NAME_LENGTH];
        const uint32 kMaxBrokenAttributes = 64;
                // sanity max value
        uint32 count = 0;
        uint32 type;
        void *data;
        size_t size;

        status_t status;
        while ((status = GetNextAttribute(name, &type, &data, &size))
                        != B_ENTRY_NOT_FOUND) {
                if (status != B_OK) {
                        printf("could not open attribute (possibly: %s): %s!\n",
                                name, strerror(status));
                        if (count++ > kMaxBrokenAttributes)
                                break;

                        continue;
                }

                ssize_t written = node->WriteAttr(name, type, 0, data, size);
                if (written < B_OK) {
                        printf("could not write attribute \"%s\": %s\n", name,
                                strerror(written));
                } else if ((size_t)written < size) {
                        printf("could only write %ld bytes (from %ld) at attribute \"%s\"\n",
                                written, size, name);
                }
        }

        // copy stats

        node->SetPermissions(fInode->mode);
        node->SetOwner(fInode->uid);
        node->SetGroup(fInode->gid);
        node->SetModificationTime(fInode->last_modified_time >> 16);
        node->SetCreationTime(fInode->create_time >> 16);

        return B_OK;
}


Inode *
Inode::Factory(Disk *disk, bfs_inode *inode, bool ownBuffer)
{
        // attributes (of a file)
        if ((inode->mode & (BFS_S_ATTR | BFS_S_ATTR_DIR)) == BFS_S_ATTR)
                return new Attribute(disk, inode, ownBuffer);

        // directories, attribute directories, indices
        if (BFS_S_ISDIR(inode->mode) || inode->mode & BFS_S_ATTR_DIR)
                return new Directory(disk, inode, ownBuffer);

        // regular files
        if (BFS_S_ISREG(inode->mode))
                return new File(disk, inode, ownBuffer);

        // symlinks (short and link in data-stream)
        if (BFS_S_ISLNK(inode->mode))
                return new Symlink(disk, inode, ownBuffer);

        return NULL;
}


Inode *
Inode::Factory(Disk *disk, block_run run)
{
        bfs_inode *inode = (bfs_inode *)malloc(disk->BlockSize());
        if (!inode)
                return NULL;

        if (disk->ReadAt(disk->ToOffset(run), inode, disk->BlockSize()) <= 0)
                return NULL;

        Inode *object = Factory(disk, inode);
        if (object == NULL)
                free(inode);

        return object;
}


Inode *
Inode::Factory(Disk *disk, Inode *inode, bool copyBuffer)
{
        bfs_inode *inodeBuffer = inode->fInode;

        if (copyBuffer) {
                bfs_inode *inodeCopy = (bfs_inode *)malloc(inodeBuffer->inode_size);
                if (!inodeCopy)
                        return NULL;

                memcpy(inodeCopy, inodeBuffer, inodeBuffer->inode_size);
                inodeBuffer = inodeCopy;
        }
        return Factory(disk, inodeBuffer, copyBuffer);
}


Inode *
Inode::EmptyInode(Disk *disk, const char *name, int32 mode)
{
        bfs_inode *inode = (bfs_inode *)malloc(disk->BlockSize());
        if (!inode)
                return NULL;

        memset(inode, 0, sizeof(bfs_inode));

        inode->magic1 = INODE_MAGIC1;
        inode->inode_size = disk->BlockSize();
        inode->mode = mode;
        inode->flags = INODE_IN_USE | (mode & BFS_S_IFDIR ? INODE_LOGGED : 0);

        if (name) {
                small_data *data = inode->small_data_start;
                data->type = FILE_NAME_TYPE;
                data->name_size = FILE_NAME_NAME_LENGTH;
                *data->Name() = FILE_NAME_NAME;
                data->data_size = strlen(name);
                strcpy((char *)data->Data(), name);
        }

        Inode *object = new (std::nothrow) Inode(disk, inode);
        if (object == NULL) {
                free(inode);
                return NULL;
        }

        object->AcquireBuffer();
                // this must not be deleted anymore!
        return object;
}


//      #pragma mark -


DataStream::DataStream(Disk *disk, bfs_inode *inode, bool ownBuffer)
        : Inode(disk,inode,ownBuffer),
        fCurrent(-1),
        fPosition(0LL)
{
}


DataStream::DataStream(const Inode &inode)
        : Inode(inode),
        fCurrent(-1),
        fPosition(0LL)
{
}


DataStream::~DataStream()
{
}


status_t
DataStream::FindBlockRun(off_t pos)
{
        NodeGetter _(this);

        if (pos > fInode->data.size)
                return B_ENTRY_NOT_FOUND;

        if (fCurrent < 0)
                fLevel = 0;

        fRunBlockEnd = fCurrent >= 0
                ? fRunFileOffset + (fRun.length << fDisk->BlockShift()) : 0LL;

        // access in current block run?

        if (fCurrent >= 0 && pos >= fRunFileOffset && pos < fRunBlockEnd)
                return B_OK;

        // find matching block run

        if (fInode->data.max_direct_range > 0
                && pos >= fInode->data.max_direct_range) {
                if (fInode->data.max_double_indirect_range > 0
                        && pos >= fInode->data.max_indirect_range) {
                        // read from double indirect blocks

                        //printf("find double indirect block: %ld,%d!\n",fInode->data.double_indirect.allocation_group,fInode->data.double_indirect.start);
                        block_run *indirect = (block_run *)fDisk->ReadBlockRun(fInode->data.double_indirect);
                        if (indirect == NULL)
                                return B_ERROR;

                        off_t start = pos - fInode->data.max_indirect_range;
                        int32 indirectSize = fDisk->BlockSize() * 16 * (fDisk->BlockSize() / sizeof(block_run));
                        int32 directSize = fDisk->BlockSize() * 4;
                        int32 index = start / indirectSize;

                        //printf("\tstart = %lld, indirectSize = %ld, directSize = %ld, index = %ld\n",start,indirectSize,directSize,index);
                        //printf("\tlook for indirect block at %ld,%d\n",indirect[index].allocation_group,indirect[index].start);
                        indirect = (block_run *)fDisk->ReadBlockRun(indirect[index]);
                        if (indirect == NULL)
                                return B_ERROR;

                        fCurrent = (start % indirectSize) / directSize;
                        fRunFileOffset = fInode->data.max_indirect_range + (index * indirectSize) + (fCurrent * directSize);
                        fRunBlockEnd = fRunFileOffset + directSize;
                        fRun = indirect[fCurrent];
                        //printf("\tfCurrent = %ld, fRunFileOffset = %lld, fRunBlockEnd = %lld, fRun = %ld,%d\n",fCurrent,fRunFileOffset,fRunBlockEnd,fRun.allocation_group,fRun.start);
                } else {
                        // access from indirect blocks

                        block_run *indirect = (block_run *)fDisk->ReadBlockRun(fInode->data.indirect);
                        if (!indirect)
                                return B_ERROR;

                        int32 indirectRuns = (fInode->data.indirect.length << fDisk->BlockShift()) / sizeof(block_run);

                        if (fLevel != 1 || pos < fRunFileOffset) {
                                fRunBlockEnd = fInode->data.max_direct_range;
                                fCurrent = -1;
                                fLevel = 1;
                        }

                        while (++fCurrent < indirectRuns) {
                                if (indirect[fCurrent].IsZero())
                                        break;

                                fRunFileOffset = fRunBlockEnd;
                                fRunBlockEnd += indirect[fCurrent].length << fDisk->BlockShift();
                                if (fRunBlockEnd > pos)
                                        break;
                        }
                        if (fCurrent == indirectRuns || indirect[fCurrent].IsZero())
                                return B_ERROR;

                        fRun = indirect[fCurrent];
                        //printf("reading from indirect block: %ld,%d\n",fRun.allocation_group,fRun.start);
                        //printf("### indirect-run[%ld] = (%ld,%d,%d), offset = %lld\n",fCurrent,fRun.allocation_group,fRun.start,fRun.length,fRunFileOffset);
                }
        } else {
                // access from direct blocks
                if (fRunFileOffset > pos) {
                        fRunBlockEnd = 0LL;
                        fCurrent = -1;
                }
                fLevel = 0;

                while (++fCurrent < NUM_DIRECT_BLOCKS) {
                        if (fInode->data.direct[fCurrent].IsZero())
                                break;

                        fRunFileOffset = fRunBlockEnd;
                        fRunBlockEnd += fInode->data.direct[fCurrent].length << fDisk->BlockShift();
                        if (fRunBlockEnd > pos)
                                break;
                }
                if (fCurrent == NUM_DIRECT_BLOCKS || fInode->data.direct[fCurrent].IsZero())
                        return B_ERROR;

                fRun = fInode->data.direct[fCurrent];
                //printf("### run[%ld] = (%ld,%d,%d), offset = %lld\n",fCurrent,fRun.allocation_group,fRun.start,fRun.length,fRunFileOffset);
        }
        return B_OK;
}


ssize_t
DataStream::ReadAt(off_t pos, void *buffer, size_t size)
{
        NodeGetter _(this);

        //printf("DataStream::ReadAt(pos = %lld,buffer = %p,size = %ld);\n",pos,buffer,size);
        // truncate size to read
        if (pos + (off_t)size > fInode->data.size) {
                if (pos > fInode->data.size)    // reading outside the file
                        return B_ERROR;

                size = fInode->data.size - pos;
                if (!size)      // there is nothing left to read
                        return 0;
        }
        ssize_t read = 0;

        //printf("### read %ld bytes at %lld\n",size,pos);
        while (size > 0) {
                status_t status = FindBlockRun(pos);
                if (status < B_OK)
                        return status;

                ssize_t bytes = min_c((off_t)size, fRunBlockEnd - pos);

                //printf("### read %ld bytes from %lld\n### --\n",bytes,fDisk->ToOffset(fRun) + pos - fRunFileOffset);
                bytes = fDisk->ReadAt(fDisk->ToOffset(fRun) + pos - fRunFileOffset,
                        buffer, bytes);
                if (bytes <= 0) {
                        if (bytes == 0) {
                                printf("could not read bytes at: %" B_PRId32 ",%d\n",
                                        fRun.allocation_group, fRun.start);
                        }
                        return bytes < 0 ? bytes : B_BAD_DATA;
                }

                buffer = (void *)((uint8 *)buffer + bytes);
                size -= bytes;
                pos += bytes;
                read += bytes;
        }
        if (read >= 0)
                return read;

        return B_IO_ERROR;
}


ssize_t
DataStream::WriteAt(off_t pos, const void *buffer, size_t size)
{
        NodeGetter _(this);

        // FIXME: truncate size -> should enlargen the file
        if (pos + (off_t)size > fInode->data.size) {
                if (pos > fInode->data.size)    // writing outside the file
                        return B_ERROR;

                size = fInode->data.size - pos;
                if (!size)      // there is nothing left to write
                        return 0;
        }
        ssize_t written = 0;

        //printf("### write %ld bytes at %lld\n",size,pos);
        while (size > 0) {
                status_t status = FindBlockRun(pos);
                if (status < B_OK)
                        return status;

                ssize_t bytes = min_c((off_t)size, fRunBlockEnd - pos);

                //printf("### write %ld bytes to %lld\n### --\n",bytes,fDisk->ToOffset(fRun) + pos - fRunFileOffset);
                bytes = fDisk->WriteAt(fDisk->ToOffset(fRun) + pos - fRunFileOffset,buffer,bytes);
                if (bytes < 0)
                        return bytes;

                buffer = (void *)((uint8 *)buffer + bytes);
                size -= bytes;
                pos += bytes;
                written += bytes;
        }
        if (written >= 0)
                return written;

        return B_IO_ERROR;
}


off_t
DataStream::Seek(off_t position, uint32 seekMode)
{
        NodeGetter _(this);

        if (seekMode == SEEK_SET)
                fPosition = position;
        else if (seekMode == SEEK_END)
                fPosition = fInode->data.size + position;
        else
                fPosition += position;

        return fPosition;
}


off_t
DataStream::Position() const
{
        return fPosition;
}


status_t
DataStream::SetSize(off_t size)
{
        NodeGetter _(this);

        // FIXME: not yet supported
        if (size > fInode->data.size || size > fInode->data.max_direct_range)
                return B_ERROR;

        if (size == fInode->data.size)
                return B_OK;

        BufferClobbered();

        fInode->data.size = size;
        fInode->data.max_direct_range = size;
        fInode->data.max_indirect_range = 0;
        fInode->data.max_double_indirect_range = 0;

        for (int32 i = 0;i < NUM_DIRECT_BLOCKS;i++) {
                if (size <= 0)
                        fInode->data.direct[i].SetTo(0, 0, 0);
                else if ((fInode->data.direct[i].length << fDisk->BlockShift()) >= size) {
                        off_t blocks = (size + fDisk->BlockSize() - 1) / fDisk->BlockSize();
                        fInode->data.direct[i].length = blocks;
                        size = 0;
                } else
                        size -= fInode->data.direct[i].length << fDisk->BlockShift();
        }

        return B_OK;
}


//      #pragma mark -


File::File(Disk *disk, bfs_inode *inode,bool ownBuffer)
        : DataStream(disk,inode,ownBuffer)
{
}


File::File(const Inode &inode)
        : DataStream(inode)
{
}


File::~File()
{
}


status_t
File::InitCheck() const
{
        status_t status = DataStream::InitCheck();
        if (status == B_OK)
                return IsFile() ? B_OK : B_ERROR;

        return status;
}


status_t
File::CopyTo(const char *root, bool fullPath, Inode::Source *source)
{
        status_t status = Inode::CopyTo(root, fullPath, source);
        if (status < B_OK)
                return status;

        BPath path(root);
        if (fullPath && Path(source))
                path.Append(Path(source));

        char *name = (char *)Name();
        if (name != NULL) {
                // changes the filename in the inode buffer (for deleted entries)
                if (!*name)
                        *name = '_';
                path.Append(name);
        } else {
                BString sub;
                sub << "__untitled " << BlockRun().allocation_group << ":"
                        << (int32)BlockRun().start;
                path.Append(sub.String());
        }
        printf("%" B_PRId32 ",%d -> %s\n", BlockRun().allocation_group,
                BlockRun().start, path.Path());

        BFile file;
        status = file.SetTo(path.Path(),
                B_WRITE_ONLY | B_CREATE_FILE | B_FAIL_IF_EXISTS);
        if (status < B_OK)
                return status;

        char buffer[fDisk->BlockSize()];
        ssize_t size;
        Seek(0, SEEK_SET);

        while ((size = Read(buffer, sizeof(buffer))) > B_OK) {
                ssize_t written = file.Write(buffer, size);
                if (written < B_OK)
                        return written;
        }

        return CopyAttributesTo(&file);
}


//      #pragma mark -


Attribute::Attribute(Disk *disk, bfs_inode *inode, bool ownBuffer)
        : File(disk, inode, ownBuffer)
{
}


Attribute::Attribute(const Inode &inode)
        : File(inode)
{
}


Attribute::~Attribute()
{
}


status_t
Attribute::InitCheck() const
{
        status_t status = DataStream::InitCheck();
        if (status == B_OK)
                return IsAttribute() ? B_OK : B_ERROR;

        return status;
}


status_t
Attribute::CopyTo(const char */*path*/, bool /*fullPath*/,
        Inode::Source */*source*/)
{
        // files and directories already copy all attributes

        // eventually, this method should be implemented to recover lost
        // attributes on the disk

        return B_OK;
}


//      #pragma mark -


Directory::Directory(Disk *disk, bfs_inode *inode, bool ownBuffer)
        : DataStream(disk, inode, ownBuffer),
        fTree(NULL)
{
}


Directory::Directory(const Inode &inode)
        : DataStream(inode),
        fTree(NULL)
{
}


Directory::~Directory()
{
        delete fTree;
}


status_t
Directory::InitCheck() const
{
        status_t status = DataStream::InitCheck();
        if (status == B_OK)
                return (IsDirectory() || IsAttributeDirectory()) ? B_OK : B_ERROR;

        return status;
}


status_t
Directory::CopyTo(const char *root, bool fullPath, Inode::Source *source)
{
        // don't copy attributes or indices
        // the recovery program should make empty files to recover lost attributes
        if (IsAttributeDirectory() || IsIndex())
                return B_OK;

        status_t status = Inode::CopyTo(root, fullPath, source);
        if (status < B_OK)
                return status;

        BPath path(root);
        if (fullPath && Path(source))
                path.Append(Path(source));

        char *name = (char *)Name();
        if (name != NULL) {
                // changes the filename in the inode buffer (for deleted entries)
                if (!*name)
                        *name = '_';
                path.Append(name);
        } else {
                // create unique name
                BString sub;
                sub << "__untitled " << BlockRun().allocation_group << ":"
                        << (int32)BlockRun().start;
                path.Append(sub.String());
        }

        BEntry entry(path.Path());
        BDirectory directory;
        if ((status = entry.GetParent(&directory)) < B_OK)
                return status;

        status = directory.CreateDirectory(path.Leaf(), NULL);
        if (status < B_OK && status != B_FILE_EXISTS)
                return status;

        if ((status = directory.SetTo(&entry)) < B_OK)
                return status;

        return CopyAttributesTo(&directory);
}


status_t
Directory::Rewind()
{
        if (!fTree) {
                status_t status = CreateTree();
                if (status < B_OK)
                        return status;
        }
        return fTree->Rewind();
}


status_t
Directory::GetNextEntry(char *name, block_run *run)
{
        status_t status;

        if (!fTree) {
                if ((status = Rewind()) < B_OK)
                        return status;
        }
        uint16 length;
        off_t offset;

        if ((status = fTree->GetNextEntry(name, &length, B_FILE_NAME_LENGTH - 1,
                        &offset)) < B_OK)
                return status;

        *run = fDisk->ToBlockRun(offset);

        return B_OK;
}


status_t
Directory::GetNextEntry(block_run *run)
{
        char name[B_FILE_NAME_LENGTH];

        return GetNextEntry(name, run);
}


status_t
Directory::Contains(const block_run *run)
{
        status_t status;

        if (!fTree) {
                if ((status = Rewind()) < B_OK)
                        return status;
        }

        block_run searchRun;
        while (GetNextEntry(&searchRun) == B_OK) {
                if (searchRun == *run)
                        return B_OK;
        }

        return B_ENTRY_NOT_FOUND;
}


status_t
Directory::Contains(const Inode *inode)
{
        status_t status;

        if (!fTree) {
                if ((status = CreateTree()) < B_OK)
                        return status;
        }

        off_t value;
        const char *name = inode->Name();
        status = B_ENTRY_NOT_FOUND;

        if (name && (status = fTree->Find((uint8 *)name, (uint16)strlen(name),
                        &value)) == B_OK) {
                if (fDisk->ToBlockRun(value) == inode->InodeBuffer()->inode_num)
                        return B_OK;

                printf("inode address do not match (%s)!\n", inode->Name());
        }

        if (status != B_OK && status != B_ENTRY_NOT_FOUND)
                return status;

        return Contains(&inode->InodeBuffer()->inode_num);
}


status_t
Directory::FindEntry(const char *name, block_run *run)
{
        status_t status;

        if (!name)
                return B_BAD_VALUE;

        if (!fTree) {
                if ((status = CreateTree()) < B_OK)
                        return status;
        }

        off_t value;

        if ((status = fTree->Find((uint8 *)name, (uint16)strlen(name),
                        &value)) >= B_OK) {
                if (run)
                        *run = fDisk->ToBlockRun(value);
                return B_OK;
        }
        return status;
}


status_t
Directory::AddEntry(Inode *inode)
{
        status_t status;
        bool created = false;

        if (!fTree) {
                status = CreateTree();
                if (status == B_OK)
                        status = fTree->Validate();

                if (status == B_BAD_DATA) {
                        //puts("bplustree corrupted!");
                        fTree = new BPlusTree(BPLUSTREE_STRING_TYPE, BPLUSTREE_NODE_SIZE,
                                false);
                        if ((status = fTree->InitCheck()) < B_OK) {
                                delete fTree;
                                fTree = NULL;
                        } else
                                created = true;
                }

                if (status < B_OK)
                        return status;
        }
        // keep all changes in memory
        fTree->SetHoldChanges(true);

        if (created) {
                // add . and ..
                fTree->Insert(".", Block());
                fTree->Insert("..", fDisk->ToBlock(Parent()));
        }

        if (inode->Flags() & INODE_DELETED)
                return B_ENTRY_NOT_FOUND;

        BString name = inode->Name();
        if (name == "") {
                name << "__file " << inode->BlockRun().allocation_group << ":"
                        << (int32)inode->BlockRun().start;
        }

        return fTree->Insert(name.String(), inode->Block());
}


status_t
Directory::CreateTree()
{
        fTree = new BPlusTree(this);

        status_t status = fTree->InitCheck();
        if (status < B_OK) {
                delete fTree;
                fTree = NULL;
                return status;
        }
        return B_OK;
}


status_t
Directory::GetTree(BPlusTree **tree)
{
        if (!fTree) {
                status_t status = CreateTree();
                if (status < B_OK)
                        return status;
        }
        *tree = fTree;
        return B_OK;
}


//      #pragma mark -


Symlink::Symlink(Disk *disk, bfs_inode *inode,bool ownBuffer)
        : Inode(disk,inode,ownBuffer)
{
}


Symlink::Symlink(const Inode &inode)
        : Inode(inode)
{
}


Symlink::~Symlink()
{
}


status_t
Symlink::InitCheck() const
{
        status_t status = Inode::InitCheck();
        if (status == B_OK)
                return IsSymlink() ? B_OK : B_ERROR;

        return status;
}


status_t
Symlink::CopyTo(const char *root, bool fullPath,Inode::Source *source)
{
        status_t status = Inode::CopyTo(root,fullPath,source);
        if (status < B_OK)
                return status;

        BPath path(root);
        if (fullPath && Path(source))
                path.Append(Path(source));

        char *name = (char *)Name();
        if (name != NULL) {
                // changes the filename in the inode buffer (for deleted entries)
                if (!*name)
                        *name = '_';
                path.Append(name);
        } else {
                // create unique name
                BString sub;
                sub << "__symlink " << BlockRun().allocation_group << ":"
                        << (int32)BlockRun().start;
                path.Append(sub.String());
        }

        BEntry entry(path.Path());
        BDirectory directory;
        if ((status = entry.GetParent(&directory)) < B_OK)
                return status;

        char to[2048];
        if (LinksTo(to,sizeof(to)) < B_OK)
                return B_ERROR;

        BSymLink link;
        status = directory.CreateSymLink(path.Leaf(),to,&link);
        if (status < B_OK && status != B_FILE_EXISTS)
                return status;

        if ((status = link.SetTo(&entry)) < B_OK)
                return status;

        return CopyAttributesTo(&link);
}


status_t
Symlink::LinksTo(char *to,size_t maxLength)
{
        if ((fInode->flags & INODE_LONG_SYMLINK) == 0) {
                strcpy(to,fInode->short_symlink);
                return B_OK;
        }

        DataStream stream(*this);
        status_t status = stream.InitCheck();
        if (status < B_OK)
                return status;

        status = stream.Read(to,maxLength);

        return status < B_OK ? status : B_OK;
}