root/src/add-ons/kernel/file_systems/ramfs/Directory.cpp
/*
 * Copyright 2007, Ingo Weinhold, ingo_weinhold@gmx.de.
 * All rights reserved. Distributed under the terms of the MIT license.
 */

#include "AllocationInfo.h"
#include "DebugSupport.h"
#include "Directory.h"
#include "Entry.h"
#include "EntryIterator.h"
#include "File.h"
#include "SymLink.h"
#include "Volume.h"

// constructor
Directory::Directory(Volume *volume)
        : Node(volume, NODE_TYPE_DIRECTORY),
          fEntries()
{
}

// destructor
Directory::~Directory()
{
        // delete all entries
        while (Entry *entry = fEntries.First()) {
                if (DeleteEntry(entry) != B_OK) {
                        FATAL("Could not delete all entries in directory.\n");
                        break;
                }
        }
}

// Link
status_t
Directory::Link(Entry *entry)
{
        if (fReferrers.IsEmpty())
                return Node::Link(entry);
        return B_IS_A_DIRECTORY;
}

// Unlink
status_t
Directory::Unlink(Entry *entry)
{
        if (entry == fReferrers.First())
                return Node::Unlink(entry);
        return B_BAD_VALUE;
}

// SetSize
status_t
Directory::SetSize(off_t /*newSize*/)
{
        return B_IS_A_DIRECTORY;
}

// GetSize
off_t
Directory::GetSize() const
{
        return 0;
}

// GetParent
Directory *
Directory::GetParent() const
{
        Entry *entry = fReferrers.First();
        return (entry ? entry->GetParent() : NULL);
}

// CreateDirectory
status_t
Directory::CreateDirectory(const char *name, Directory **directory)
{
        status_t error = (name && directory ? B_OK : B_BAD_VALUE);
        if (error == B_OK) {
                // create directory
                if (Directory *node = new(nothrow) Directory(GetVolume())) {
                        error = _CreateCommon(node, name);
                                // deletes the node on failure
                        if (error == B_OK)
                                *directory = node;
                } else
                        SET_ERROR(error, B_NO_MEMORY);
        }
        return error;
}

// CreateFile
status_t
Directory::CreateFile(const char *name, File **file)
{
        status_t error = (name && file ? B_OK : B_BAD_VALUE);
        if (error == B_OK) {
                // create file
                if (File *node = new(nothrow) File(GetVolume())) {
                        error = _CreateCommon(node, name);
                                // deletes the node on failure
                        if (error == B_OK)
                                *file = node;
                } else
                        SET_ERROR(error, B_NO_MEMORY);
        }
        return error;
}

// CreateSymLink
status_t
Directory::CreateSymLink(const char *name, const char *path, SymLink **symLink)
{
        status_t error = (name && symLink ? B_OK : B_BAD_VALUE);
        if (error == B_OK) {
                // create symlink
                if (SymLink *node = new(nothrow) SymLink(GetVolume())) {
                        error = node->SetLinkedPath(path);
                        if (error == B_OK) {
                                error = _CreateCommon(node, name);
                                        // deletes the node on failure
                                if (error == B_OK)
                                        *symLink = node;
                        } else
                                delete node;
                } else
                        SET_ERROR(error, B_NO_MEMORY);
        }
        return error;
}

// AddEntry
status_t
Directory::AddEntry(Entry *entry)
{
        status_t error = (entry && !entry->GetParent() ? B_OK : B_BAD_VALUE);
        if (error == B_OK) {
                fEntries.Insert(entry);
                entry->SetParent(this);
                error = GetVolume()->EntryAdded(GetID(), entry);
                if (error == B_OK) {
                        MarkModified(B_STAT_MODIFICATION_TIME);
                } else {
                        fEntries.Remove(entry);
                        entry->SetParent(NULL);
                }
        }
        return error;
}

// CreateEntry
status_t
Directory::CreateEntry(Node *node, const char *name, Entry **_entry)
{
        status_t error = (node ? B_OK : B_BAD_VALUE);
        if (error == B_OK) {
                // create an entry
                Entry *entry = new(nothrow) Entry(name);
                if (entry) {
                        error = entry->InitCheck();
                        if (error == B_OK) {
                                // link to the node
                                error = entry->Link(node);
                                if (error == B_OK) {
                                        // add the entry
                                        error = AddEntry(entry);
                                        if (error == B_OK) {
                                                if (_entry)
                                                        *_entry = entry;
                                        } else {
                                                // failure: unlink the node
                                                entry->Unlink();
                                        }
                                }
                        }
                        // delete the entry on failure
                        if (error != B_OK)
                                delete entry;
                } else
                        SET_ERROR(error, B_NO_MEMORY);
        }
        return error;
}

// RemoveEntry
status_t
Directory::RemoveEntry(Entry *entry)
{
        status_t error = (entry && entry->GetParent() == this ? B_OK
                                                                                                                  : B_BAD_VALUE);
        if (error == B_OK) {
                // move all iterators pointing to the entry to the next entry
                if (recursive_lock_lock(&GetVolume()->GetIteratorLock()) == B_OK) {
                        // set the iterators' current entry
                        Entry *nextEntry = fEntries.GetNext(entry);
                        DoublyLinkedList<EntryIterator> *iterators
                                = entry->GetEntryIteratorList();
                        for (EntryIterator *iterator = iterators->First();
                                 iterator;
                                 iterator = iterators->GetNext(iterator)) {
                                iterator->SetCurrent(nextEntry, true);
                        }
                        // Move the iterators from one list to the other, or just remove
                        // them, if there is no next entry.
                        if (nextEntry) {
                                DoublyLinkedList<EntryIterator> *nextIterators
                                        = nextEntry->GetEntryIteratorList();
                                nextIterators->TakeFrom(iterators);
                        } else
                                iterators->RemoveAll();
                        recursive_lock_unlock(&GetVolume()->GetIteratorLock());
                } else
                        error = B_ERROR;
                // remove the entry
                if (error == B_OK) {
                        error = GetVolume()->EntryRemoved(GetID(), entry);
                        if (error == B_OK) {
                                fEntries.Remove(entry);
                                entry->SetParent(NULL);
                                MarkModified(B_STAT_MODIFICATION_TIME);
                        }
                }
        }
        return error;
}

// DeleteEntry
status_t
Directory::DeleteEntry(Entry *entry)
{
        status_t error = RemoveEntry(entry);
        if (error == B_OK) {
                error = entry->Unlink();
                if (error == B_OK)
                        delete entry;
                else {
                        FATAL("Failed to Unlink() entry %p from node %" B_PRIdINO "!\n", entry,
                                   entry->GetNode()->GetID());
                        AddEntry(entry);
                }
        }
        return error;
}

// FindEntry
status_t
Directory::FindEntry(const char *name, Entry **_entry) const
{
        status_t error = (name && _entry ? B_OK : B_BAD_VALUE);
        if (error == B_OK) {
/*
                Entry *entry = NULL;
                while (GetNextEntry(&entry) == B_OK) {
                        if (!strcmp(entry->GetName(), name)) {
                                *_entry = entry;
                                return B_OK;
                        }
                }
                error = B_ENTRY_NOT_FOUND;
*/
                error = GetVolume()->FindEntry(GetID(), name, _entry);
        }
        return error;
}

// FindNode
status_t
Directory::FindNode(const char *name, Node **node) const
{
        status_t error = (name && node ? B_OK : B_BAD_VALUE);
        Entry *entry = NULL;
        if (error == B_OK && (error = FindEntry(name, &entry)) == B_OK)
                *node = entry->GetNode();
        return error;
}

// FindAndGetNode
status_t
Directory::FindAndGetNode(const char *name, Node **node, Entry **_entry) const
{
        status_t error = (name && node ? B_OK : B_BAD_VALUE);
        Entry *entry = NULL;
        if (error == B_OK && (error = FindEntry(name, &entry)) == B_OK) {
                *node = entry->GetNode();
                if (_entry)
                        *_entry = entry;
                error = GetVolume()->GetVNode(*node);
        }
        return error;
}

// GetPreviousEntry
status_t
Directory::GetPreviousEntry(Entry **entry) const
{
        status_t error = (entry ? B_OK : B_BAD_VALUE);
        if (error == B_OK) {
                if (!*entry)
                        *entry = fEntries.Last();
                else if ((*entry)->GetParent() == this)
                        *entry = fEntries.GetPrevious(*entry);
                else
                        error = B_BAD_VALUE;
                if (error == B_OK && !*entry)
                        error = B_ENTRY_NOT_FOUND;
        }
        return error;
}

// GetNextEntry
status_t
Directory::GetNextEntry(Entry **entry) const
{
        status_t error = (entry ? B_OK : B_BAD_VALUE);
        if (error == B_OK) {
                if (!*entry)
                        *entry = fEntries.First();
                else if ((*entry)->GetParent() == this)
                        *entry = fEntries.GetNext(*entry);
                else
                        error = B_BAD_VALUE;
                if (error == B_OK && !*entry)
                        error = B_ENTRY_NOT_FOUND;
        }
        return error;
}

// GetAllocationInfo
void
Directory::GetAllocationInfo(AllocationInfo &info)
{
        Node::GetAllocationInfo(info);
        info.AddDirectoryAllocation();
        Entry *entry = NULL;
        while (GetNextEntry(&entry) == B_OK)
                entry->GetAllocationInfo(info);
}

// _CreateCommon
status_t
Directory::_CreateCommon(Node *node, const char *name)
{
        status_t error = node->InitCheck();
        if (error == B_OK) {
                // add node to directory
                error = CreateEntry(node, name);
        }
        if (error != B_OK)
                delete node;
        return error;
}