root/src/add-ons/kernel/file_systems/bfs/FileSystemVisitor.cpp
/*
 * Copyright 2002-2012, Axel Dörfler, axeld@pinc-software.de.
 * Copyright 2012, Andreas Henriksson, sausageboy@gmail.com
 * This file may be used under the terms of the MIT License.
 */


//! Traversing the file system


#include "FileSystemVisitor.h"

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


FileSystemVisitor::FileSystemVisitor(Volume* volume)
        :
        fVolume(volume),
        fIterator(NULL)
{
}


FileSystemVisitor::~FileSystemVisitor()
{
        Stop();
}


//      #pragma mark - file system traversal


/*! Visit the next inode.

        \return
        - \c B_ENTRY_NOT_FOUND : All nodes specified in Start() have been
          traversed
        - Any error code returned by the overridable functions
*/
status_t
FileSystemVisitor::Next()
{
        status_t status;

        while (true) {
                const char* name = NULL;
                char treeName[B_FILE_NAME_LENGTH];
                Inode* inode;
                Vnode vnode;

                if (fIterator == NULL) {
                        if (!fStack.Pop(&fCurrent)) {
                                // we're done
                                return B_ENTRY_NOT_FOUND;
                        }

                        // open inode
                        vnode.SetTo(fVolume, fCurrent);
                        status = vnode.Get(&inode);

                        // release the reference we acquired when pushing the node to
                        // the stack
                        put_vnode(fVolume->FSVolume(), fVolume->ToVnode(fCurrent));

                        if (status != B_OK) {
                                if (inode != NULL && inode->IsDeleted())
                                        continue;

                                status = OpenInodeFailed(status, fVolume->ToBlock(fCurrent),
                                        NULL, NULL, NULL);
                                if (status == B_OK)
                                        continue;
                                return status;
                        }

                        if (inode->IsContainer()) {
                                // open directory
                                BPlusTree* tree = inode->Tree();
                                if (tree == NULL) {
                                        status = OpenBPlusTreeFailed(inode);
                                        if (status == B_OK)
                                                continue;
                                        return status;
                                }

                                fParent = inode;

                                // get iterator for the next directory
                                fIterator = new(std::nothrow) TreeIterator(tree);
                                if (fIterator == NULL)
                                        RETURN_ERROR(B_NO_MEMORY);

                                // the inode must stay locked in memory until the iterator
                                // is freed
                                vnode.Keep();
                        }
                } else {
                        uint16 length;
                        ino_t id;

                        status_t status = fIterator->GetNextEntry(treeName, &length,
                                B_FILE_NAME_LENGTH, &id);
                        if (status != B_OK) {
                                // we no longer need this iterator
                                delete fIterator;
                                fIterator = NULL;

                                // unlock the directory's inode from memory
                                put_vnode(fVolume->FSVolume(),
                                        fVolume->ToVnode(fCurrent));

                                if (status == B_ENTRY_NOT_FOUND) {
                                        // We iterated over all entries already, just go on
                                        // to the next
                                        continue;
                                }

                                // iterating over the B+tree failed
                                //return TreeIterationFailed(status, fParent);
                                status = TreeIterationFailed(status, fParent);
                                if (status == B_OK)
                                        continue;
                                return status;
                        }

                        // ignore "." and ".." entries
                        if (!strcmp(treeName, ".") || !strcmp(treeName, ".."))
                                continue;

                        vnode.SetTo(fVolume, id);
                        status = vnode.Get(&inode);
                        if (status != B_OK) {
                                status = OpenInodeFailed(status, id, fParent, treeName,
                                        fIterator);
                                if (status == B_OK)
                                        continue;
                                return status;
                        }

                        status = VisitDirectoryEntry(inode, fParent, treeName);
                        if (status != B_OK)
                                return status;

                        if (inode->IsContainer() && !inode->IsIndex()) {
                                // push the directory on the stack, it will be visited after
                                // its children
                                fStack.Push(inode->BlockRun());

                                // the inode may be deleted behind our back, we keep a
                                // reference so we can check for this
                                vnode.Keep();
                                continue;
                        }

                        name = treeName;
                }

                // If the inode has an attribute directory that we want to visit,
                // push it on the stack
                if ((fFlags & VISIT_ATTRIBUTE_DIRECTORIES)
                                && !inode->Attributes().IsZero()) {
                        fStack.Push(inode->Attributes());

                        // We may already be keeping the associated Vnode, so we can't
                        // just call vnode.Keep() here, but rather acquire another reference
                        // to it specifically.
                        Vnode attrNode(fVolume, inode->Attributes());
                        attrNode.Keep();
                }

                bool visitingCurrentDirectory = inode->BlockRun() == fCurrent;

                status = VisitInode(inode, name);

                // the inode id is allowed to change in the VisitInode() call, so we
                // may need to change the inode reference
                if (visitingCurrentDirectory)
                        fCurrent = inode->BlockRun();

                return status;
        }
        // is never reached
}


/*! Start/restart traversal. \a flags is used to specify the nodes visited:
        - \c VISIT_REGULAR :    Visit the nodes that are reachable by traversing
                                                        the file system from the root directory.
        - \c VISIT_INDICES :    Visit the index directory and indices
        - \c VISIT_REMOVED :    Visit removed vnodes
        - \c VISIT_ATTRIBUTE_DIRECTORIES :      Visit the attribute directory and
                                                                                attributes of files that have them.
*/
void
FileSystemVisitor::Start(uint32 flags)
{
        // initialize state
        Stop();
        fCurrent.SetTo(0, 0, 0);
        fParent = NULL;
        fStack.MakeEmpty();

        fFlags = flags;

        if (fFlags & VISIT_REGULAR) {
                Vnode vnode(fVolume, fVolume->Root());
                vnode.Keep();
                fStack.Push(fVolume->Root());
        }

        if (fFlags & VISIT_INDICES) {
                Vnode vnode(fVolume, fVolume->Indices());
                vnode.Keep();
                fStack.Push(fVolume->Indices());
        }

        if (fFlags & VISIT_REMOVED) {
                // Put removed vnodes to the stack -- they are not reachable by
                // traversing the file system anymore.
                InodeList::Iterator iterator = fVolume->RemovedInodes().GetIterator();
                while (Inode* inode = iterator.Next()) {
                        Vnode vnode(fVolume, inode->ID());
                        vnode.Keep();
                        fStack.Push(inode->BlockRun());
                }
        }
}


/*! Free aquired resources. This function is called from the destructor.
*/
void
FileSystemVisitor::Stop()
{
        if (fIterator != NULL) {
                delete fIterator;
                fIterator = NULL;

                // the current directory inode is still locked in memory
                put_vnode(fVolume->FSVolume(), fVolume->ToVnode(fCurrent));
        }

        // release the references to the vnodes on the stack
        block_run run;
        while (fStack.Pop(&run))
                put_vnode(fVolume->FSVolume(), fVolume->ToVnode(run));
}


//      #pragma mark - overrideable actions


/*! Called when an inode is opened while iterating through its parent
        directory. Note that this function is not called for inodes which
        don't have a proper parent directory, namely:
        - The root directory
        - The index directory
        - Attribute directories
        - Removed nodes

        Return \c B_OK to continue traversing, any other error code to stop
        traversal and propagate the error to the caller of Next(). In the
        latter case, VisitInode() will never be called for this inode.
*/
status_t
FileSystemVisitor::VisitDirectoryEntry(Inode* inode, Inode* parent,
        const char* treeName)
{
        return B_OK;
}


/*! Called for every inode, some time after VisitDirectoryEntry(). For
        directories, all subdirectories will be traversed before this
        function is called.

        Unless traversal has been stopped by an error handling function, all
        calls to Next() end by invoking this function, and the return value
        is passed along to the caller.

        This call may change the inode ID.
*/
status_t
FileSystemVisitor::VisitInode(Inode* inode, const char* treeName)
{
        return B_OK;
}


/*! Called when opening an inode fails. If the failure happened while
        iterating through a directory, \a parent, \a treeName and \a iterator
        will be provided. Otherwise, they will be \c NULL.

        \return
        - \c B_OK : The traversal continues to the next inode
        - Other : The traversal stops and the error code is propagated to the
          caller of Next().
*/
status_t
FileSystemVisitor::OpenInodeFailed(status_t reason, ino_t id, Inode* parent,
        char* treeName, TreeIterator* iterator)
{
        return B_OK;
}


/*! Called when opening a b+tree fails.

        \return
        - \c B_OK : The traversal continues to the next inode
        - Other : The traversal stops and the error code is propagated to the
          caller of Next().
*/
status_t
FileSystemVisitor::OpenBPlusTreeFailed(Inode* inode)
{
        return B_OK;
}


/*! Called if we failed to get the next node while iterating a container.

        \return
        - \c B_OK : The traversal continues to the next inode
        - Other : The traversal stops and the error code is propagated to the
          caller of Next().
*/
status_t
FileSystemVisitor::TreeIterationFailed(status_t reason, Inode* parent)
{
        return B_OK;
}