/*************************************************************************
 *
 *  OpenOffice.org - a multi-platform office productivity suite
 *
 *  $RCSfile: storpage.cxx,v $
 *
 *  $Revision: 1.6 $
 *
 *  last change: $Author: hr $ $Date: 2006-06-20 00:34:06 $
 *
 *  The Contents of this file are made available subject to
 *  the terms of GNU Lesser General Public License Version 2.1.
 *
 *
 *    GNU Lesser General Public License Version 2.1
 *    =============================================
 *    Copyright 2005 by Sun Microsystems, Inc.
 *    901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License version 2.1, as published by the Free Software Foundation.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with this library; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *    MA  02111-1307  USA
 *
 ************************************************************************/

#include <storpage.hxx>

#ifndef _SAL_TYPES_H_
#include <sal/types.h>
#endif

#ifndef _RTL_MEMORY_H_
#include <rtl/memory.h>
#endif
#ifndef _RTL_STRING_H_
#include <rtl/string.h>
#endif
#ifndef _RTL_REF_HXX_
#include <rtl/ref.hxx>
#endif

#ifndef _OSL_DIAGNOSE_H_
#include <osl/diagnose.h>
#endif
#ifndef _OSL_MUTEX_HXX_
#include <osl/mutex.hxx>
#endif

#ifndef _STORE_TYPES_H_
#include <store/types.h>
#endif
#ifndef _STORE_OBJECT_HXX_
#include <store/object.hxx>
#endif
#ifndef _STORE_LOCKBYTE_HXX_
#include <store/lockbyte.hxx>
#endif

#ifndef _STORE_STORBASE_HXX_
#include <storbase.hxx>
#endif
#ifndef _STORE_STORCACH_HXX_
#include <storcach.hxx>
#endif
#ifndef _STORE_STORDATA_HXX_
#include <stordata.hxx>
#endif
#ifndef _STORE_STORTREE_HXX_
#include <stortree.hxx>
#endif

using namespace store;

/*========================================================================
 *
 * OStorePageManager implementation.
 *
 *======================================================================*/
const sal_uInt32 OStorePageManager::m_nTypeId = sal_uInt32(0x62190120);

/*
 * OStorePageManager.
 */
OStorePageManager::OStorePageManager (void)
    : m_pCache    (NULL),
      m_pDirect   (NULL),
      m_pData     (NULL),
      m_nPageSize (0)
{
    // Node pages.
    m_pNode[0] = NULL;
    m_pNode[1] = NULL;
    m_pNode[2] = NULL;

    // Indirect pages.
    m_pLink[0] = NULL;
    m_pLink[1] = NULL;
    m_pLink[2] = NULL;
}

/*
 * ~OStorePageManager.
 */
OStorePageManager::~OStorePageManager (void)
{
    delete m_pCache;
    delete m_pDirect;
    delete m_pData;

    delete m_pNode[0];
    delete m_pNode[1];
    delete m_pNode[2];

    delete m_pLink[0];
    delete m_pLink[1];
    delete m_pLink[2];
}

/*
 * isKindOf.
 */
sal_Bool SAL_CALL OStorePageManager::isKindOf (sal_uInt32 nTypeId)
{
    return (nTypeId == m_nTypeId);
}

/*
 * initialize (two-phase construction).
 * Precond: none.
 */
storeError OStorePageManager::initializeManager (
    ILockBytes      *pLockBytes,
    storeAccessMode  eAccessMode,
    sal_uInt16       nPageSize)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard(*this);

    // Check arguments.
    if (!pLockBytes)
        return store_E_InvalidParameter;

    // Initialize base.
    storeError eErrCode = base::initialize (pLockBytes, eAccessMode);
    if (eErrCode != store_E_None)
    {
        // Check mode and reason.
        if (eErrCode != store_E_NotExists)
            return eErrCode;

        if (eAccessMode == store_AccessReadWrite)
            return store_E_NotExists;
        if (eAccessMode == store_AccessReadOnly)
            return store_E_NotExists;

        // Create.
        eErrCode = base::create (nPageSize);
        if (eErrCode != store_E_None)
            return eErrCode;
    }

    // Obtain page size.
    eErrCode = base::getPageSize (m_nPageSize);
    OSL_POSTCOND(
        eErrCode == store_E_None,
        "OStorePageManager::initialize(): getPageSize() failed");
    if (eErrCode != store_E_None)
        return eErrCode;

    // Cleanup.
    __STORE_DELETEZ (m_pCache);
    __STORE_DELETEZ (m_pDirect);
    __STORE_DELETEZ (m_pData);

    __STORE_DELETEZ (m_pNode[0]);
    __STORE_DELETEZ (m_pNode[1]);
    __STORE_DELETEZ (m_pNode[2]);

    __STORE_DELETEZ (m_pLink[0]);
    __STORE_DELETEZ (m_pLink[1]);
    __STORE_DELETEZ (m_pLink[2]);

    // Initialize page buffers.
    m_pNode[0] = new(m_nPageSize) page(m_nPageSize);
    if (eAccessMode != store_AccessReadOnly)
    {
        m_pNode[1] = new(m_nPageSize) page(m_nPageSize);
        m_pNode[2] = new(m_nPageSize) page(m_nPageSize);
    }

    // Initialize page cache.
    m_pCache = new OStorePageCache();

    // Done.
    return eErrCode;
}

/*
 * free (unmanaged).
 * Precond: initialized, writeable.
 */
storeError OStorePageManager::free (OStorePageObject &rPage)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard(*this);

    // Check precond.
    if (!self::isValid())
        return store_E_InvalidAccess;

    if (!base::isWriteable())
        return store_E_AccessViolation;

    // Check for cacheable page.
    OStorePageData &rData = rPage.getData();
    if (rData.m_aGuard.m_nMagic == STORE_MAGIC_BTREENODE)
    {
        // Invalidate cache entry.
        storeError eErrCode = m_pCache->invalidate (rData.m_aDescr);
        if (eErrCode != store_E_None)
            return eErrCode;
    }

    // Free page.
    return base::free (rPage);
}

/*
 * load (unmanaged).
 * Precond: initialized.
 */
storeError OStorePageManager::load (OStorePageObject &rPage)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard(*this);

    // Check precond.
    if (!self::isValid())
        return store_E_InvalidAccess;

    // Check for cacheable page.
    OStorePageData &rData = rPage.getData();
    if (rData.m_aGuard.m_nMagic == STORE_MAGIC_BTREENODE)
    {
        // Save PageDescriptor.
        OStorePageDescriptor aDescr (rData.m_aDescr);

        // Load (cached) page.
        storeError eErrCode = m_pCache->load (aDescr, rData, *this);
        if (eErrCode != store_E_None)
        {
            // Check for pending verification.
            if (eErrCode != store_E_Pending)
                return eErrCode;

            // Verify page.
            eErrCode = rPage.verify (aDescr);
            if (eErrCode != store_E_None)
                return eErrCode;
        }

#ifdef OSL_BIGENDIAN
        // Swap to internal representation.
        rPage.swap (aDescr);
#endif /* OSL_BIGENDIAN */

        // Done.
        return store_E_None;
    }

    // Load (uncached) page.
    return base::load (rPage);
}

/*
 * save (unmanaged).
 * Precond: initialized, writeable.
 */
storeError OStorePageManager::save (OStorePageObject &rPage)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard(*this);

    // Check precond.
    if (!self::isValid())
        return store_E_InvalidAccess;

    if (!base::isWriteable())
        return store_E_AccessViolation;

    // Check for cacheable page.
    OStorePageData &rData = rPage.getData();
    if (rData.m_aGuard.m_nMagic == STORE_MAGIC_BTREENODE)
    {
        // Save PageDescriptor.
        OStorePageDescriptor aDescr (rData.m_aDescr);

#ifdef OSL_BIGENDIAN
        // Swap to external representation.
        rPage.swap (aDescr);
#endif /* OSL_BIGENDIAN */

        // Guard page.
        rPage.guard (aDescr);

        // Save (cached) page.
#if 0  /* EXPERIMENTAL */
        storeError eErrCode = m_pCache->update (
            aDescr, rData, *this, NULL,
            OStorePageCache::UPDATE_WRITE_DELAYED);
#else
        storeError eErrCode = m_pCache->update (
            aDescr, rData, *this, NULL,
            OStorePageCache::UPDATE_WRITE_THROUGH);
#endif /* EXPERIMENTAL */

#ifdef OSL_BIGENDIAN
        // Swap back to internal representation.
        rPage.swap (aDescr);
#endif /* OSL_BIGENDIAN */

        // Mark page clean.
        if (eErrCode == store_E_None)
            rPage.clean();

        // Done.
        return eErrCode;
    }

    // Save (uncached) page.
    return base::save (rPage);
}

/*
 * flush.
 * Precond: initialized.
 */
storeError OStorePageManager::flush (void)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard(*this);

    // Check precond.
    if (!self::isValid())
        return store_E_InvalidAccess;

    // Check access mode.
    if (!base::isWriteable())
        return store_E_None;

    // Flush cache.
    if (m_pCache->flush (*this, NULL) != store_E_None) {
        OSL_POSTCOND(
            false, "OStorePageManager::flush(): cache::flush() failed");
    }

    // Flush base.
    return base::flush();
}

/*
 * find (w/o split()).
 * Internal: Precond: initialized, readable, exclusive access.
 */
storeError OStorePageManager::find (const entry &rEntry, page &rPage)
{
    // Load RootNode.
    OStoreBTreeRootObject aRoot (rPage);
    aRoot.location (m_nPageSize);

    storeError eErrCode = load (aRoot);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Check current Page.
    while (rPage.depth())
    {
        // Find next page.
        sal_uInt16 i = rPage.find(rEntry), n = rPage.usageCount();
        if (!(i < n))
        {
            // Path to entry not exists (Must not happen(?)).
            return store_E_NotExists;
        }

        // Check address.
        sal_uInt32 nAddr = rPage.m_pData[i].m_aLink.m_nAddr;
        if (nAddr == STORE_PAGE_NULL)
        {
            // Path to entry not exists (Must not happen(?)).
            return store_E_NotExists;
        }

        // Load next page.
        node aNode (rPage);
        aNode.location (nAddr);

        eErrCode = load (aNode);
        if (eErrCode != store_E_None)
            return eErrCode;
    }

    // Done.
    return store_E_None;
}

/*
 * find (possibly with split()).
 * Internal: Precond: initialized, writeable, exclusive access.
 */
storeError OStorePageManager::find (
    const entry &rEntry, page &rPage, page &rPageL, page &rPageR)
{
    // Load RootNode.
    OStoreBTreeRootObject aRoot (rPage);
    aRoot.location (m_nPageSize);

    storeError eErrCode = load (aRoot);
    if (eErrCode != store_E_None)
    {
        // Check existence.
        if (eErrCode != store_E_NotExists)
            return eErrCode;

        // Pre-allocate left most entry (ugly, but we can't insert to left).
        rPage.insert (0, entry());
        rPage.m_pData[0].m_aKey.m_nLow = OStorePageGuard::crc32 (0, "/", 1);

        // Allocate RootNode.
        eErrCode = base::allocate (aRoot, ALLOCATE_EOF);
        if (eErrCode != store_E_None)
            return eErrCode;
    }
    else
    {
        // Check for RootNode split.
        if (aRoot.querySplit())
        {
            // Split root.
            eErrCode = aRoot.split (0, rPageL, rPageR, *this);
            if (eErrCode != store_E_None)
                return eErrCode;
        }
    }

    // Check current Page.
    while (rPage.depth())
    {
        // Find next page.
        sal_uInt16 i = rPage.find (rEntry), n = rPage.usageCount();
        if (!(i < n))
        {
            // Path to entry not exists (Must not happen(?)).
            return store_E_NotExists;
        }

        // Check address.
        sal_uInt32 nAddr = rPage.m_pData[i].m_aLink.m_nAddr;
        if (nAddr == STORE_PAGE_NULL)
        {
            // Path to entry not exists (Must not happen(?)).
            return store_E_NotExists;
        }

        // Load next page.
        node aNode (rPageL);
        aNode.location (nAddr);

        eErrCode = load (aNode);
        if (eErrCode != store_E_None)
            return eErrCode;

        // Check for node split.
        if (rPageL.querySplit())
        {
            // Split node.
            node aParent (rPage);
            eErrCode = aParent.split (i, rPageL, rPageR, *this);
            if (eErrCode != store_E_None)
                return eErrCode;

            // Restart.
            continue;
        }
        else
        {
            // Let next page be current.
            rPage = rPageL;
            continue;
        }
    }

    // Done.
    return store_E_None;
}

/*
 * remove (possibly down from root).
 * Internal: Precond: initialized, writeable, exclusive access.
 */
storeError OStorePageManager::remove (
    entry &rEntry, page &rPage, page &rPageL)
{
    // Load RootNode.
    OStoreBTreeRootObject aRoot (rPage);
    aRoot.location (m_nPageSize);

    storeError eErrCode = load (aRoot);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Check index.
    sal_uInt16 i = rPage.find (rEntry), n = rPage.usageCount();
    if (!(i < n))
    {
        // Path to entry not exists (Must not happen(?)).
        return store_E_NotExists;
    }

    // Compare entry.
    entry::CompareResult result = rEntry.compare (rPage.m_pData[i]);

    // Iterate down until equal match.
    while ((result == entry::COMPARE_GREATER) && (rPage.depth() > 0))
    {
        // Check link address.
        sal_uInt32 nAddr = rPage.m_pData[i].m_aLink.m_nAddr;
        if (nAddr == STORE_PAGE_NULL)
        {
            // Path to entry not exists (Must not happen(?)).
            return store_E_NotExists;
        }

        // Load link page.
        node aNode (rPage);
        aNode.location (nAddr);

        eErrCode = load (aNode);
        if (eErrCode != store_E_None)
            return eErrCode;

        // Check index.
        i = rPage.find (rEntry), n = rPage.usageCount();
        if (!(i < n))
        {
            // Path to entry not exists (Must not happen(?)).
            return store_E_NotExists;
        }

        // Compare entry.
        result = rEntry.compare (rPage.m_pData[i]);
    }

    OSL_POSTCOND(
        result != entry::COMPARE_LESS,
        "OStorePageManager::remove(): find failed");

    // Check entry comparison.
    if (result == entry::COMPARE_LESS)
    {
        // Must not happen.
        return store_E_Unknown;
    }

    // Remove down from current page (recursive).
    node aNode (rPage);
    return aNode.remove (i, rEntry, rPageL, *this, NULL);
}

/*
 * load.
 * Precond: initialized.
 */
storeError OStorePageManager::load (
    const OStorePageKey       &rKey,
    OStoreDirectoryPageObject &rPage)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard(*this);

    // Check precond.
    if (!self::isValid())
        return store_E_InvalidAccess;

    // Setup BTree entry.
    entry e;
    e.m_aKey = rKey;

    // Find NodePage.
    storeError eErrCode = find (e, *m_pNode[0]);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Find Index.
    sal_uInt16 i = m_pNode[0]->find(e), n = m_pNode[0]->usageCount();
    if (!(i < n))
    {
        // Page not present.
        return store_E_NotExists;
    }

    // Check for exact match.
    if (!(e.compare (m_pNode[0]->m_pData[i]) == entry::COMPARE_EQUAL))
    {
        // Page not present.
        return store_E_NotExists;
    }

    // Existing entry. Check address.
    sal_uInt32 nAddr = m_pNode[0]->m_pData[i].m_aLink.m_nAddr;
    if (nAddr == STORE_PAGE_NULL)
    {
        // Page not present.
        return store_E_NotExists;
    }

    // Load page.
    rPage.location (nAddr);
    return load (rPage);
}

/*
 * save.
 * Precond: initialized, writeable.
 */
storeError OStorePageManager::save (
    const OStorePageKey       &rKey,
    OStoreDirectoryPageObject &rPage)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard(*this);

    // Check precond.
    if (!self::isValid())
        return store_E_InvalidAccess;

    if (!base::isWriteable())
        return store_E_AccessViolation;

    // Setup BTree entry.
    entry e;
    e.m_aKey = rKey;

    // Find NodePage.
    storeError eErrCode = find (e, *m_pNode[0], *m_pNode[1], *m_pNode[2]);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Find Index.
    sal_uInt16 i = m_pNode[0]->find(e), n = m_pNode[0]->usageCount();
    if (i < n)
    {
        // Compare entry.
        entry::CompareResult result = e.compare (m_pNode[0]->m_pData[i]);
        OSL_POSTCOND(
            result != entry::COMPARE_LESS,
            "OStorePageManager::save(): find failed");

        // Check result.
        if (result == entry::COMPARE_LESS)
        {
            // Must not happen.
            return store_E_Unknown;
        }

        if (result == entry::COMPARE_EQUAL)
        {
            // Existing entry. Check address.
            sal_uInt32 nAddr = m_pNode[0]->m_pData[i].m_aLink.m_nAddr;
            if (nAddr == STORE_PAGE_NULL)
            {
                // Allocate page.
                eErrCode = base::allocate (rPage);
                if (eErrCode != store_E_None)
                    return eErrCode;

                // Modify page address.
                m_pNode[0]->m_pData[i].m_aLink.m_nAddr = rPage.location();

                // Save modified NodePage.
                node aNode (*m_pNode[0]);
                return save (aNode);
            }
            else
            {
                // Save page.
                rPage.location (nAddr);
                return save (rPage);
            }
        }
    }

    // Allocate.
    eErrCode = base::allocate (rPage);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Insert.
    e.m_aLink.m_nAddr = rPage.location();
    m_pNode[0]->insert (i + 1, e);

    // Save modified NodePage.
    node aNode (*m_pNode[0]);
    return save (aNode);
}

/*
 * attrib [nAttrib = ((nAttrib & ~nMask1) | nMask2)].
 * Precond: initialized.
 */
storeError OStorePageManager::attrib (
    const OStorePageKey &rKey,
    sal_uInt32           nMask1,
    sal_uInt32           nMask2,
    sal_uInt32          &rAttrib)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard(*this);

    // Check precond.
    if (!self::isValid())
        return store_E_InvalidAccess;

    // Setup BTree entry.
    entry e;
    e.m_aKey = rKey;

    // Find NodePage.
    storeError eErrCode = find (e, *m_pNode[0]);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Find Index.
    sal_uInt16 i = m_pNode[0]->find(e), n = m_pNode[0]->usageCount();
    if (!(i < n))
    {
        // Page not present.
        return store_E_NotExists;
    }

    // Check for exact match.
    if (!(e.compare (m_pNode[0]->m_pData[i]) == entry::COMPARE_EQUAL))
    {
        // Page not present.
        return store_E_NotExists;
    }

    // Existing entry.
    e = m_pNode[0]->m_pData[i];
    if (nMask1 != nMask2)
    {
        // Evaluate new attributes.
        sal_uInt32 nAttrib = e.m_nAttrib;

        nAttrib &= ~nMask1;
        nAttrib |=  nMask2;

        if (nAttrib != e.m_nAttrib)
        {
            // Check access mode.
            if (base::isWriteable())
            {
                // Set new attributes.
                e.m_nAttrib = nAttrib;
                m_pNode[0]->m_pData[i] = e;

                // Save modified NodePage.
                node aNode (*m_pNode[0]);
                eErrCode = save (aNode);
            }
            else
            {
                // Access denied.
                eErrCode = store_E_AccessViolation;
            }
        }
    }

    // Obtain current attributes.
    rAttrib = e.m_nAttrib;
    return eErrCode;
}

/*
 * link (insert 'Source' as hardlink to 'Destination').
 * Precond: initialized, writeable.
 */
storeError OStorePageManager::link (
    const OStorePageKey &rSrcKey,
    const OStorePageKey &rDstKey)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard(*this);

    // Check precond.
    if (!self::isValid())
        return store_E_InvalidAccess;

    if (!base::isWriteable())
        return store_E_AccessViolation;

    // Setup 'Destination' BTree entry.
    entry e;
    e.m_aKey = rDstKey;

    // Find 'Destination' NodePage.
    storeError eErrCode = find (e, *m_pNode[0]);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Find 'Destination' Index.
    sal_uInt16 i = m_pNode[0]->find(e), n = m_pNode[0]->usageCount();
    if (!(i < n))
    {
        // Page not present.
        return store_E_NotExists;
    }

    // Check for exact match.
    if (!(e.compare (m_pNode[0]->m_pData[i]) == entry::COMPARE_EQUAL))
    {
        // Page not present.
        return store_E_NotExists;
    }

    // Existing entry. Check address.
    e = m_pNode[0]->m_pData[i];
    if (e.m_aLink.m_nAddr == STORE_PAGE_NULL)
    {
        // Page not present.
        return store_E_NotExists;
    }

    // Setup 'Source' BTree entry.
    e.m_aKey    = rSrcKey;
    e.m_nAttrib = STORE_ATTRIB_ISLINK;

    // Find 'Source' NodePage.
    eErrCode = find (e, *m_pNode[0], *m_pNode[1], *m_pNode[2]);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Find 'Source' Index.
    i = m_pNode[0]->find(e), n = m_pNode[0]->usageCount();
    if (i < n)
    {
        // Compare entry.
        entry::CompareResult result = e.compare (m_pNode[0]->m_pData[i]);
        OSL_POSTCOND(
            result != entry::COMPARE_LESS,
            "OStorePageManager::link(): find failed");

        // Check result.
        if (result == entry::COMPARE_LESS)
        {
            // Must not happen.
            return store_E_Unknown;
        }

        if (result == entry::COMPARE_EQUAL)
        {
            // Existing 'Source' entry.
            return store_E_AlreadyExists;
        }
    }

    // Insert 'Source' entry.
    m_pNode[0]->insert (i + 1, e);

    node aNode (*m_pNode[0]);
    return save (aNode);
}

/*
 * symlink (insert 'Source' DirectoryPage as symlink to 'Destination').
 * Precond: initialized, writeable.
 */
storeError OStorePageManager::symlink (
    const rtl_String    *pSrcPath,
    const rtl_String    *pSrcName,
    const OStorePageKey &rDstKey)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard(*this);

    // Check precond.
    if (!self::isValid())
        return store_E_InvalidAccess;

    if (!base::isWriteable())
        return store_E_AccessViolation;

    // Check 'Source' parameter.
    storeError eErrCode = store_E_InvalidParameter;
    if (!(pSrcPath && pSrcName))
        return eErrCode;

    // Setup 'Source' page key.
    OStorePageKey aSrcKey;
    eErrCode = OStorePageNameBlock::namei (pSrcPath, pSrcName, aSrcKey);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Setup 'Source' BTree entry.
    entry e;
    e.m_aKey = aSrcKey;

    // Find 'Source' NodePage.
    eErrCode = find (e, *m_pNode[0], *m_pNode[1], *m_pNode[2]);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Find 'Source' Index.
    sal_uInt16 i = m_pNode[0]->find(e), n = m_pNode[0]->usageCount();
    if (i < n)
    {
        // Compare entry.
        entry::CompareResult result = e.compare (m_pNode[0]->m_pData[i]);
        OSL_POSTCOND(
            result != entry::COMPARE_LESS,
            "OStorePageManager::symlink(): find failed");

        // Check result.
        if (result == entry::COMPARE_LESS)
        {
            // Must not happen.
            return store_E_Unknown;
        }

        if (result == entry::COMPARE_EQUAL)
        {
            // Existing 'Source' entry.
            return store_E_AlreadyExists;
        }
    }

    // Initialize directory page buffer.
    if (m_pDirect)
        m_pDirect->initialize();
    if (!m_pDirect)
        m_pDirect = new(m_nPageSize) inode(m_nPageSize);
    if (!m_pDirect)
        return store_E_OutOfMemory;

    // Setup as 'Source' directory page.
    m_pDirect->m_aNameBlock.m_aKey = aSrcKey;
    rtl_copyMemory (
        &m_pDirect->m_aNameBlock.m_pData[0],
        pSrcName->buffer, pSrcName->length);

    // Store 'Destination' page key.
    OStorePageKey aDstKey (rDstKey);
#ifdef OSL_BIGENDIAN
    aDstKey.swap(); // Swap to external representation.
#endif /* OSL_BIGENDIAN */
    rtl_copyMemory (&m_pDirect->m_pData[0], &aDstKey, sizeof(aDstKey));

    // Mark 'Source' as symbolic link to 'Destination'.
    OStoreDirectoryPageObject aPage (*m_pDirect);
    aPage.attrib (STORE_ATTRIB_ISLINK);
    aPage.dataLength (sal_uInt32(sizeof(aDstKey)));

    // Allocate and save 'Source' directory page.
    eErrCode = base::allocate (aPage);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Insert 'Source' entry.
    e.m_aLink.m_nAddr = aPage.location();
    m_pNode[0]->insert (i + 1, e);

    // Save modified NodePage.
    node aNode (*m_pNode[0]);
    return save (aNode);
}

/*
 * rename.
 * Precond: initialized, writeable.
 */
storeError OStorePageManager::rename (
    const OStorePageKey &rSrcKey,
    const rtl_String    *pDstPath,
    const rtl_String    *pDstName)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard(*this);

    // Check precond.
    if (!self::isValid())
        return store_E_InvalidAccess;

    if (!base::isWriteable())
        return store_E_AccessViolation;

    // Check 'Destination' parameter.
    storeError eErrCode = store_E_InvalidParameter;
    if (!(pDstPath && pDstName))
        return eErrCode;

    // Setup 'Destination' page key.
    OStorePageKey aDstKey;
    eErrCode = OStorePageNameBlock::namei (pDstPath, pDstName, aDstKey);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Setup 'Source' BTree entry.
    entry e;
    e.m_aKey = rSrcKey;

    // Find 'Source' NodePage.
    eErrCode = find (e, *m_pNode[0]);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Find 'Source' Index.
    sal_uInt16 i = m_pNode[0]->find(e), n = m_pNode[0]->usageCount();
    if (!(i < n))
    {
        // Page not present.
        return store_E_NotExists;
    }

    // Check for exact match.
    if (!(e.compare (m_pNode[0]->m_pData[i]) == entry::COMPARE_EQUAL))
    {
        // Page not present.
        return store_E_NotExists;
    }

    // Existing 'Source' entry. Check address.
    e = m_pNode[0]->m_pData[i];
    if (e.m_aLink.m_nAddr == STORE_PAGE_NULL)
    {
        // Page not present.
        return store_E_NotExists;
    }

    // Check for hardlink.
    if (!(e.m_nAttrib & STORE_ATTRIB_ISLINK))
    {
        // Check directory page buffer.
        if (!m_pDirect)
            m_pDirect = new(m_nPageSize) inode(m_nPageSize);
        if (!m_pDirect)
            return store_E_OutOfMemory;

        // Load directory page.
        OStoreDirectoryPageObject aPage (*m_pDirect);
        aPage.location (e.m_aLink.m_nAddr);

        eErrCode = base::load (aPage);
        if (eErrCode != store_E_None)
            return eErrCode;

        // Check for directory.
        if (aPage.attrib() & STORE_ATTRIB_ISDIR)
        {
            // Ugly, but necessary (backward compatibility).
            aDstKey.m_nLow = OStorePageGuard::crc32 (aDstKey.m_nLow, "/", 1);
        }
    }

    // Let 'Source' entry be 'Destination' entry.
    e.m_aKey = aDstKey;

    // Find 'Destination' NodePage.
    eErrCode = find (e, *m_pNode[0], *m_pNode[1], *m_pNode[2]);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Find 'Destination' Index.
    i = m_pNode[0]->find(e), n = m_pNode[0]->usageCount();
    if (i < n)
    {
        // Compare entry.
        entry::CompareResult result = e.compare (m_pNode[0]->m_pData[i]);
        OSL_POSTCOND(
            result != entry::COMPARE_LESS,
            "OStorePageManager::rename(): find failed");

        // Check result.
        if (result == entry::COMPARE_LESS)
        {
            // Must not happen.
            return store_E_Unknown;
        }

        if (result == entry::COMPARE_EQUAL)
        {
            // Existing 'Destination' entry.
            return store_E_AlreadyExists;
        }
    }

    // Insert 'Destination' entry.
    node aNode (*m_pNode[0]);
    m_pNode[0]->insert (i + 1, e);

    eErrCode = save (aNode);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Check for hardlink.
    if (!(e.m_nAttrib & STORE_ATTRIB_ISLINK))
    {
        // Setup 'Destination' NameBlock.
        sal_Int32 nDstLen = pDstName->length;
        rtl_copyMemory (
            &m_pDirect->m_aNameBlock.m_pData[0],
            pDstName->buffer, nDstLen);
        rtl_zeroMemory (
            &m_pDirect->m_aNameBlock.m_pData[nDstLen],
            STORE_MAXIMUM_NAMESIZE - nDstLen);
        m_pDirect->m_aNameBlock.m_aKey = e.m_aKey;

        // Save directory page.
        OStoreDirectoryPageObject aPage (*m_pDirect);
        aPage.location (e.m_aLink.m_nAddr);

        eErrCode = base::save (aPage);
        if (eErrCode != store_E_None)
            return eErrCode;
    }

    // Remove 'Source' entry.
    e.m_aKey = rSrcKey;
    return remove (e, *m_pNode[0], *m_pNode[1]);
}

/*
 * remove.
 * Precond: initialized, writeable.
 */
storeError OStorePageManager::remove (const OStorePageKey &rKey)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard(*this);

    // Check precond.
    if (!self::isValid())
        return store_E_InvalidAccess;

    if (!base::isWriteable())
        return store_E_AccessViolation;

    // Setup BTree entry.
    entry e;
    e.m_aKey = rKey;

    // Find NodePage.
    storeError eErrCode = find (e, *m_pNode[0]);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Find Index.
    sal_uInt16 i = m_pNode[0]->find(e), n = m_pNode[0]->usageCount();
    if (!(i < n))
    {
        // Page not present.
        return store_E_NotExists;
    }

    // Check for exact match.
    if (!(e.compare (m_pNode[0]->m_pData[i]) == entry::COMPARE_EQUAL))
    {
        // Page not present.
        return store_E_NotExists;
    }

    // Existing entry. Check address.
    e = m_pNode[0]->m_pData[i];
    if (e.m_aLink.m_nAddr == STORE_PAGE_NULL)
    {
        // Page not present.
        return store_E_NotExists;
    }

    // Check for hardlink.
    if (!(e.m_nAttrib & STORE_ATTRIB_ISLINK))
    {
        // Check directory page buffer.
        if (!m_pDirect)
            m_pDirect = new(m_nPageSize) inode(m_nPageSize);
        if (!m_pDirect)
            return store_E_OutOfMemory;

        // Load directory page.
        OStoreDirectoryPageObject aPage (*m_pDirect);
        aPage.location (e.m_aLink.m_nAddr);

        eErrCode = base::load (aPage);
        if (eErrCode != store_E_None)
            return eErrCode;

        // Acquire page write access.
        OStorePageDescriptor aDescr (m_pDirect->m_aDescr);
        eErrCode = base::acquirePage (aDescr, store_AccessReadWrite);
        if (eErrCode != store_E_None)
            return eErrCode;

        // Check for symbolic link.
        if (!(aPage.attrib() & STORE_ATTRIB_ISLINK))
        {
            // Ordinary inode. Determine 'Data' scope.
            inode::ChunkScope eScope = m_pDirect->scope (aPage.dataLength());
            if (eScope == inode::SCOPE_EXTERNAL)
            {
                // External 'Data' scope. Check data page buffer.
                if (!m_pData)
                    m_pData = new(m_nPageSize) data(m_nPageSize);
                if (!m_pData)
                    return store_E_OutOfMemory;

                // Truncate all external data pages.
                OStoreDataPageObject aData (*m_pData);
                eErrCode = aPage.truncate (
                    0, m_pLink[0], m_pLink[1], m_pLink[2], aData, *this);
                if (eErrCode != store_E_None)
                    return eErrCode;
            }

            // Truncate internal data page.
            rtl_zeroMemory (&m_pDirect->m_pData[0], m_pDirect->capacity());
            aPage.dataLength (0);
        }

        // Release page write access.
        eErrCode = base::releasePage (aDescr);

        // Release and free directory page.
        eErrCode = base::free (aPage);
    }

    // Remove entry.
    return remove (e, *m_pNode[0], *m_pNode[1]);
}

/*
 * iterate.
 * Precond: initialized.
 * ToDo: skip hardlink entries.
 */
storeError OStorePageManager::iterate (
    OStorePageKey    &rKey,
    OStorePageObject &rPage,
    sal_uInt32       &rAttrib)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard(*this);

    // Check precond.
    if (!self::isValid())
        return store_E_InvalidAccess;

    // Setup BTree entry.
    entry e;
    e.m_aKey = rKey;

    // Find NodePage.
    storeError eErrCode = find (e, *m_pNode[0]);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Find Index.
    sal_uInt16 i = m_pNode[0]->find(e), n = m_pNode[0]->usageCount();
    if (!(i < n))
    {
        // Not found.
        return store_E_NotExists;
    }

    // Compare entry.
    entry::CompareResult result = e.compare (m_pNode[0]->m_pData[i]);
    OSL_POSTCOND(
        result != entry::COMPARE_LESS,
        "OStorePageManager::iterate(): find failed");

    // Check result.
    if (result == entry::COMPARE_LESS)
    {
        // Must not happen.
        return store_E_Unknown;
    }

    // GreaterEqual. Found next entry.
    e = m_pNode[0]->m_pData[i];

    // Setup result.
    rKey    = e.m_aKey;
    rAttrib = e.m_nAttrib;

    rPage.location (e.m_aLink.m_nAddr);

    // Done.
    return store_E_None;
}

/*
 * RebuildContext.
 */
struct RebuildContext
{
    /** Representation.
    */
    rtl::Reference<OStorePageBIOS> m_xBIOS;
    OStorePageBIOS::ScanContext    m_aCtx;

    /** Construction.
     */
    RebuildContext (void)
        : m_xBIOS (new OStorePageBIOS())
    {}

    /** initialize (PageBIOS and ScanContext).
    */
    storeError initialize (ILockBytes *pLockBytes, sal_uInt32 nMagic = 0)
    {
        storeError eErrCode = store_E_InvalidParameter;
        if (pLockBytes)
        {
            m_xBIOS->initialize (pLockBytes, store_AccessReadOnly);
            eErrCode = m_xBIOS->scanBegin (m_aCtx, nMagic);
        }
        return eErrCode;
    }

    /** initialize (ScanContext).
    */
    storeError initialize (sal_uInt32 nMagic = 0)
    {
        return m_xBIOS->scanBegin (m_aCtx, nMagic);
    }

    /** load (next ScanContext matching page).
    */
    storeError load (OStorePageObject &rPage)
    {
        if (m_aCtx.isValid())
            return m_xBIOS->scanNext (m_aCtx, rPage);
        else
            return store_E_CantSeek;
    }

    /** getPageSize.
     */
    storeError getPageSize (sal_uInt16 &rnPageSize)
    {
        return m_xBIOS->getPageSize (rnPageSize);
    }
};

/*
 * rebuild.
 * Precond: none.
 */
storeError OStorePageManager::rebuild (
    ILockBytes *pSrcLB, ILockBytes *pDstLB)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard(*this);

    // Check arguments.
    storeError eErrCode = store_E_InvalidParameter;
    if (!(pSrcLB && pDstLB))
        return eErrCode;

    // Initialize 'Source' rebuild context.
    RebuildContext aCtx;
    eErrCode = aCtx.initialize (pSrcLB, STORE_MAGIC_DIRECTORYPAGE);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Obtain 'Source' page size.
    sal_uInt16 nPageSize = 0;
    eErrCode = aCtx.getPageSize (nPageSize);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Initialize as 'Destination' with 'Source' page size.
    eErrCode = self::initializeManager (pDstLB, store_AccessCreate, nPageSize);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Initialize directory and data page buffers.
    if (!m_pDirect)
        m_pDirect = new(m_nPageSize) inode(m_nPageSize);
    if (!m_pData)
        m_pData = new(m_nPageSize) data(m_nPageSize);
    if (!(m_pDirect && m_pData))
        return store_E_OutOfMemory;

    // Initialize 'Source' directory page.
    inode *pDirect = new(m_nPageSize) inode(m_nPageSize);
    if (!pDirect)
        return store_E_OutOfMemory;

    // Scan 'Source' directory pages.
    OStoreDirectoryPageObject aSrcPage (*pDirect);
    while ((eErrCode = aCtx.load(aSrcPage)) == store_E_None)
    {
        // Obtain page key and data length.
        OStorePageKey aKey (pDirect->m_aNameBlock.m_aKey);
        sal_uInt32 nDataLen = pDirect->m_aDataBlock.m_nDataLen;

        // Determine data page scope.
        inode::ChunkScope eScope = pDirect->scope (nDataLen);
        if (eScope == inode::SCOPE_INTERNAL)
        {
            // Internal scope. Just insert directory node.
            eErrCode = save (aKey, aSrcPage);
            if (eErrCode != store_E_None)
                break;
        }
        else
        {
            // External scope.
            OStoreDirectoryPageObject aDstPage (*m_pDirect);
            rtl_copyMemory (m_pDirect, pDirect, m_nPageSize);

            m_pDirect->m_aDataBlock.initialize();
            m_pDirect->m_aDataBlock.m_nDataLen = m_pDirect->capacity();

            // Insert 'Destination' directory page.
            eErrCode = save (aKey, aDstPage);
            if (eErrCode != store_E_None)
                break;

            // Determine data page count.
            inode::ChunkDescriptor aDescr (
                nDataLen - m_pDirect->capacity(), m_pData->capacity());

            sal_uInt32 i, n = aDescr.m_nPage;
            if (aDescr.m_nOffset) n += 1;

            // Copy data pages.
            OStoreDataPageObject aData (*m_pData);
            for (i = 0; i < n; i++)
            {
                // Re-initialize data page size.
                m_pData->m_aDescr.m_nSize = m_nPageSize;

                // Read 'Source' data page.
                OStorePageBIOS &rBIOS  = *(aCtx.m_xBIOS);
                osl::Mutex     &rMutex = rBIOS;

                eErrCode = aSrcPage.get (
                    i, m_pLink[0], m_pLink[1], m_pLink[2],
                    aData, rBIOS, &rMutex);
                if (eErrCode != store_E_None)
                    continue;

                // Write 'Destination' data page.
                eErrCode = aDstPage.put (
                    i, m_pLink[0], m_pLink[1], m_pLink[2],
                    aData, *this, NULL);
            }

            // Update 'Destination' directory page.
            m_pDirect->m_aDataBlock.m_nDataLen = nDataLen;
            eErrCode = base::save (aDstPage);
        }
    }

    // Save directory scan results.
    flush();

    // Scan 'Source' BTree nodes.
    page *pNode = new(m_nPageSize) page(m_nPageSize);
    node aNode (*pNode);
    entry e;

    aCtx.initialize (STORE_MAGIC_BTREENODE);
    while ((eErrCode = aCtx.load(aNode)) == store_E_None)
    {
        // Check for leaf node.
        if (pNode->depth() == 0)
        {
            sal_uInt16 i, n = pNode->usageCount();
            for (i = 0; i < n; i++)
            {
                e = pNode->m_pData[i];
                if (e.m_nAttrib & STORE_ATTRIB_ISLINK)
                {
                    // Hard link.
                    aSrcPage.location (e.m_aLink.m_nAddr);
                    pDirect->m_aDescr.m_nSize = m_nPageSize;

                    eErrCode = aCtx.m_xBIOS->load (aSrcPage);
                    if (eErrCode == store_E_None)
                    {
                        OStorePageKey aDstKey (pDirect->m_aNameBlock.m_aKey);
                        eErrCode = link (e.m_aKey, aDstKey);
                    }
                    e.m_nAttrib &= ~STORE_ATTRIB_ISLINK;
                }

                if (e.m_nAttrib)
                {
                    // Ordinary attributes.
                    sal_uInt32 nAttrib = 0;
                    eErrCode = attrib (e.m_aKey, 0, e.m_nAttrib, nAttrib);
                }
            }
        }
    }

    // Save BTree node scan results.
    flush();

    // Cleanup.
    delete pDirect;
    delete pNode;

    // Done.
    return store_E_None;
}
