/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org 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 version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_store.hxx"

#include "storbios.hxx"

#include "sal/types.h"
#include "sal/macros.h"

#include "rtl/alloc.h"
#include "rtl/ref.hxx"

#include "osl/diagnose.h"
#include "osl/mutex.hxx"

#include "store/types.h"
#include "object.hxx"
#include "lockbyte.hxx"
#include "storcach.hxx"

using namespace store;

/*========================================================================
 *
 * OStoreSuperBlock.
 *
 *======================================================================*/
#define STORE_MAGIC_SUPERBLOCK sal_uInt32(0x484D5343)

struct OStoreSuperBlock
{
    typedef OStorePageGuard      G;
    typedef OStorePageDescriptor D;
    typedef OStorePageLink       L;

    /** Representation.
     */
    G          m_aGuard;
    D          m_aDescr;
    sal_uInt32 m_nMarked;
    L          m_aMarked;
    sal_uInt32 m_nUnused;
    L          m_aUnused;

    /** theSize.
     */
    static const size_t theSize = sizeof(G) + sizeof(D) + 2 * (sizeof(L) + sizeof(sal_uInt32));

    /** Construction.
     */
    explicit OStoreSuperBlock (sal_uInt16 nPageSize)
        : m_aGuard  (STORE_MAGIC_SUPERBLOCK),
          m_aDescr  (nPageSize, nPageSize, STORE_MINIMUM_PAGESIZE),
          m_nMarked (store::htonl(0)),
          m_aMarked (0),
          m_nUnused (store::htonl(0)),
          m_aUnused (0)
    {}

    OStoreSuperBlock (const OStoreSuperBlock & rhs)
        : m_aGuard  (rhs.m_aGuard),
          m_aDescr  (rhs.m_aDescr),
          m_nMarked (rhs.m_nMarked),
          m_aMarked (rhs.m_aMarked),
          m_nUnused (rhs.m_nUnused),
          m_aUnused (rhs.m_aUnused)
    {}

    OStoreSuperBlock& operator= (const OStoreSuperBlock & rhs)
    {
        m_aGuard  = rhs.m_aGuard;
        m_aDescr  = rhs.m_aDescr;
        m_nMarked = rhs.m_nMarked;
        m_aMarked = rhs.m_aMarked;
        m_nUnused = rhs.m_nUnused;
        m_aUnused = rhs.m_aUnused;
        return *this;
    }

    /** Comparison.
     */
    sal_Bool operator== (const OStoreSuperBlock & rhs) const
    {
        return ((m_aGuard  == rhs.m_aGuard ) &&
                (m_aDescr  == rhs.m_aDescr ) &&
                (m_nMarked == rhs.m_nMarked) &&
                (m_aMarked == rhs.m_aMarked) &&
                (m_nUnused == rhs.m_nUnused) &&
                (m_aUnused == rhs.m_aUnused)    );
    }

    /** unused(Count|Head|Insert|Remove|Reset).
     */
    sal_uInt32 unusedCount (void) const
    {
        return store::ntohl(m_nUnused);
    }
    const L& unusedHead (void) const
    {
        return m_aUnused;
    }
    void unusedInsert (const L& rLink)
    {
        sal_uInt32 nUnused = unusedCount();
        m_nUnused = store::htonl(nUnused + 1);
        m_aUnused = rLink;
    }
    void unusedRemove (const L& rLink)
    {
        sal_uInt32 nUnused = unusedCount();
        m_nUnused = store::htonl(nUnused - 1);
        m_aUnused = rLink;
    }
    void unusedReset (void)
    {
        m_nUnused = store::htonl(0);
        m_aUnused = L(0);
    }

    /** guard (external representation).
     */
    void guard()
    {
        sal_uInt32 nCRC32 = 0;
        nCRC32 = rtl_crc32 (nCRC32, &m_aGuard.m_nMagic, sizeof(sal_uInt32));
        nCRC32 = rtl_crc32 (nCRC32, &m_aDescr, theSize - sizeof(G));
        m_aGuard.m_nCRC32 = store::htonl(nCRC32);
    }

    /** verify (external representation).
     */
    storeError verify() const
    {
        sal_uInt32 nMagic = store::ntohl(m_aGuard.m_nMagic);
        if (nMagic != STORE_MAGIC_SUPERBLOCK)
            return store_E_WrongFormat;

        sal_uInt32 nCRC32 = 0;
        nCRC32 = rtl_crc32 (nCRC32, &m_aGuard.m_nMagic, sizeof(sal_uInt32));
        nCRC32 = rtl_crc32 (nCRC32, &m_aDescr, theSize - sizeof(G));
        if (m_aGuard.m_nCRC32 != store::htonl(nCRC32))
            return store_E_InvalidChecksum;
        else
            return store_E_None;
    }
};

/*========================================================================
 *
 * SuperBlockPage interface.
 *
 *======================================================================*/
namespace store
{

struct SuperBlockPage
{
    typedef OStoreSuperBlock SuperBlock;

    /** Representation.
     */
    SuperBlock m_aSuperOne;
    SuperBlock m_aSuperTwo;

    /** theSize.
     */
    static const size_t     theSize     = 2 * SuperBlock::theSize;
    static const sal_uInt16 thePageSize = theSize;
    STORE_STATIC_ASSERT(STORE_MINIMUM_PAGESIZE >= thePageSize);

    /** Allocation.
     */
    static void * operator new (size_t n) SAL_THROW(())
    {
        return rtl_allocateMemory (sal::static_int_cast<sal_Size>(n));
    }
    static void operator delete (void * p, size_t) SAL_THROW(())
    {
        rtl_freeMemory (p);
    }

    static void * operator new (size_t, sal_uInt16 nPageSize) SAL_THROW(())
    {
        return rtl_allocateZeroMemory (sal::static_int_cast<sal_Size>(nPageSize));
    }
    static void operator delete (void * p, sal_uInt16) SAL_THROW(())
    {
        rtl_freeMemory (p);
    }

    /** Construction.
     */
    explicit SuperBlockPage (sal_uInt16 nPageSize = thePageSize)
        : m_aSuperOne(nPageSize),
          m_aSuperTwo(nPageSize)
    {}

    /** save.
     */
    storeError save (OStorePageBIOS & rBIOS, sal_uInt32 nSize = theSize)
    {
        m_aSuperOne.guard();
        m_aSuperTwo = m_aSuperOne;
        return rBIOS.write (0, this, nSize);
    }

    /** Page allocation.
     */
    storeError unusedHead (
        OStorePageBIOS & rBIOS,
        PageData &       rPageHead);

    storeError unusedPop (
        OStorePageBIOS & rBIOS,
        PageData const & rPageHead);

    storeError unusedPush (
        OStorePageBIOS & rBIOS,
        sal_uInt32       nAddr);

    /** verify (with repair).
     */
    storeError verify (OStorePageBIOS & rBIOS);
};

} // namespace store

/*========================================================================
 *
 * SuperBlockPage implementation.
 *
 *======================================================================*/
/*
 * unusedHead(): get freelist head (alloc page, step 1).
 */
storeError SuperBlockPage::unusedHead (OStorePageBIOS & rBIOS, PageData & rPageHead)
{
    storeError eErrCode = verify (rBIOS);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Check freelist head.
    OStorePageLink const aListHead (m_aSuperOne.unusedHead());
    if (aListHead.location() == 0)
    {
        // Freelist empty, see SuperBlock::ctor().
        rPageHead.location (STORE_PAGE_NULL);
        return store_E_None;
    }

    // Load PageHead.
    eErrCode = rBIOS.read (aListHead.location(), &rPageHead, PageData::theSize);
    if (eErrCode != store_E_None)
        return eErrCode;

    eErrCode = rPageHead.verify (aListHead.location());
    if (eErrCode != store_E_None)
        return eErrCode;

    // Verify page is unused.
    sal_uInt32 const nAddr = rPageHead.m_aUnused.location();
    OSL_POSTCOND(nAddr != STORE_PAGE_NULL, "store::SuperBlock::unusedHead(): page not free");
    if (nAddr == STORE_PAGE_NULL)
    {
        // Page in use.
        rPageHead.location (STORE_PAGE_NULL);

        // Recovery: Reset freelist to empty.
        m_aSuperOne.unusedReset();
        eErrCode = save (rBIOS);
    }
    return eErrCode;
}

/*
 * unusedPop(): pop freelist head (alloc page, step 2).
 */
storeError SuperBlockPage::unusedPop (OStorePageBIOS & rBIOS, PageData const & rPageHead)
{
    sal_uInt32 const nAddr = rPageHead.m_aUnused.location();
    OSL_PRECOND(nAddr != STORE_PAGE_NULL, "store::SuperBlock::unusedPop(): page not free");
    if (nAddr == STORE_PAGE_NULL)
        return store_E_CantSeek;

    // Pop from FreeList.
    OStorePageLink const aListHead (nAddr);
    m_aSuperOne.unusedRemove (aListHead);
    return save (rBIOS);
}

/*
 * unusedPush(): push new freelist head.
 */
storeError SuperBlockPage::unusedPush (OStorePageBIOS & rBIOS, sal_uInt32 nAddr)
{
    storeError eErrCode = verify (rBIOS);
    if (eErrCode != store_E_None)
        return eErrCode;

    PageData aPageHead;
    eErrCode = rBIOS.read (nAddr, &aPageHead, PageData::theSize);
    if (eErrCode != store_E_None)
        return eErrCode;

    eErrCode = aPageHead.verify (nAddr);
    if (eErrCode != store_E_None)
        return eErrCode;

    aPageHead.m_aUnused = m_aSuperOne.unusedHead();
    aPageHead.guard (nAddr);

    eErrCode = rBIOS.write (nAddr, &aPageHead, PageData::theSize);
    if (eErrCode != store_E_None)
        return eErrCode;

    OStorePageLink const aListHead (nAddr);
    m_aSuperOne.unusedInsert(aListHead);
    return save (rBIOS);
}

/*
 * verify (with repair).
 */
storeError SuperBlockPage::verify (OStorePageBIOS & rBIOS)
{
    // Verify 1st copy.
    storeError eErrCode = m_aSuperOne.verify();
    if (eErrCode == store_E_None)
    {
        // Ok. Verify 2nd copy.
        eErrCode = m_aSuperTwo.verify();
        if (eErrCode == store_E_None)
        {
            // Ok. Ensure identical copies (1st copy wins).
            if (!(m_aSuperOne == m_aSuperTwo))
            {
                // Different. Replace 2nd copy with 1st copy.
                m_aSuperTwo = m_aSuperOne;

                // Write back.
                if (rBIOS.isWriteable())
                    eErrCode = rBIOS.write (0, this, theSize);
                else
                    eErrCode = store_E_None;
            }
        }
        else
        {
            // Failure. Replace 2nd copy with 1st copy.
            m_aSuperTwo = m_aSuperOne;

            // Write back.
            if (rBIOS.isWriteable())
                eErrCode = rBIOS.write (0, this, theSize);
            else
                eErrCode = store_E_None;
        }
    }
    else
    {
        // Failure. Verify 2nd copy.
        eErrCode = m_aSuperTwo.verify();
        if (eErrCode == store_E_None)
        {
            // Ok. Replace 1st copy with 2nd copy.
            m_aSuperOne = m_aSuperTwo;

            // Write back.
            if (rBIOS.isWriteable())
                eErrCode = rBIOS.write (0, this, theSize);
            else
                eErrCode = store_E_None;
        }
        else
        {
            // Double Failure.
            OSL_TRACE("OStoreSuperBlockPage::verify(): double failure.\n");
        }
    }

    // Done.
    return eErrCode;
}

/*========================================================================
 *
 * OStorePageBIOS::Ace implementation.
 *
 *======================================================================*/
OStorePageBIOS::Ace::Ace()
  : m_next (this), m_prev (this), m_addr (STORE_PAGE_NULL), m_used (0)
{}

OStorePageBIOS::Ace::~Ace()
{
  m_next->m_prev = m_prev, m_prev->m_next = m_next;
}

int
SAL_CALL OStorePageBIOS::Ace::constructor (void * obj, void * /* arg */)
{
  Ace * ace = static_cast<Ace*>(obj);
  ace->m_next = ace->m_prev = ace;
  return 1;
}

OStorePageBIOS::Ace *
OStorePageBIOS::Ace::find (OStorePageBIOS::Ace * head, sal_uInt32 addr)
{
  OStorePageBIOS::Ace * entry;
  for (entry = head->m_next; entry != head; entry = entry->m_next)
  {
    if (entry->m_addr >= addr)
      return entry;
  }
  return head;
}

void
OStorePageBIOS::Ace::insert (OStorePageBIOS::Ace * head, OStorePageBIOS::Ace * entry)
{
  // insert entry at queue tail (before head).
  entry->m_next = head;
  entry->m_prev = head->m_prev;
  head->m_prev = entry;
  entry->m_prev->m_next = entry;
}

/*========================================================================
 *
 * OStorePageBIOS::AceCache interface.
 *
 *======================================================================*/
namespace store
{

class OStorePageBIOS::AceCache
{
  rtl_cache_type * m_ace_cache;

public:
  static AceCache & get();

  OStorePageBIOS::Ace *
  create (sal_uInt32 addr, sal_uInt32 used = 1);

  void
  destroy (OStorePageBIOS::Ace * ace);

protected:
  AceCache();
  ~AceCache();
};

} // namespace store

/*========================================================================
 *
 * OStorePageBIOS::AceCache implementation.
 *
 *======================================================================*/
extern "C"  typedef  int (SAL_CALL * ace_constructor_type)(void*,void*);

OStorePageBIOS::AceCache &
OStorePageBIOS::AceCache::get()
{
  static AceCache g_ace_cache;
  return g_ace_cache;
}

OStorePageBIOS::AceCache::AceCache()
{
  m_ace_cache = rtl_cache_create (
    "store_ace_cache",
    sizeof (OStorePageBIOS::Ace),
    0, // objalign
   reinterpret_cast<ace_constructor_type>( OStorePageBIOS::Ace::constructor),
    0, // destructor,
    0, // reclaim,
    0, // userarg,
    0, // default source,
    0  // flags
    );
}

OStorePageBIOS::AceCache::~AceCache()
{
  rtl_cache_destroy (m_ace_cache), m_ace_cache = 0;
}

OStorePageBIOS::Ace *
OStorePageBIOS::AceCache::create (sal_uInt32 addr, sal_uInt32 used)
{
  Ace * ace = static_cast<Ace*>(rtl_cache_alloc (m_ace_cache));
  if (ace != 0)
  {
    // verify invariant state.
    OSL_ASSERT((ace->m_next == ace) && (ace->m_prev == ace));

    // initialize.
    ace->m_addr = addr;
    ace->m_used = used;
  }
  return ace;
}

void
OStorePageBIOS::AceCache::destroy (OStorePageBIOS::Ace * ace)
{
  if (ace != 0)
  {
    // remove from queue (if any).
    ace->m_next->m_prev = ace->m_prev, ace->m_prev->m_next = ace->m_next;

    // restore invariant state.
    ace->m_next = ace->m_prev = ace;

    // return to cache.
    rtl_cache_free (m_ace_cache, ace);
  }
}

/*========================================================================
 *
 * OStorePageBIOS implementation.
 *
 *======================================================================*/
/*
 * OStorePageBIOS.
 */
OStorePageBIOS::OStorePageBIOS (void)
    : m_xLockBytes (NULL),
      m_pSuper     (NULL),
      m_bWriteable (false)
{
}

/*
 * ~OStorePageBIOS.
 */
OStorePageBIOS::~OStorePageBIOS (void)
{
    cleanup_Impl();
}

/*
 * initialize.
 * Precond: none.
 */
storeError OStorePageBIOS::initialize (
    ILockBytes *    pLockBytes,
    storeAccessMode eAccessMode,
    sal_uInt16 &    rnPageSize)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard (m_aMutex);

    // Initialize.
    storeError eErrCode = initialize_Impl (pLockBytes, eAccessMode, rnPageSize);
    if (eErrCode != store_E_None)
    {
        // Cleanup.
        cleanup_Impl();
    }
    return eErrCode;
}

/*
 * initialize_Impl.
 * Internal: Precond: exclusive access.
 */
storeError OStorePageBIOS::initialize_Impl (
    ILockBytes *    pLockBytes,
    storeAccessMode eAccessMode,
    sal_uInt16 &    rnPageSize)
{
    // Cleanup.
    cleanup_Impl();

    // Initialize.
    m_xLockBytes = pLockBytes;
    if (!m_xLockBytes.is())
        return store_E_InvalidParameter;
    m_bWriteable = (eAccessMode != store_AccessReadOnly);

    // Check access mode.
    storeError eErrCode = store_E_None;
    if (eAccessMode != store_AccessCreate)
    {
        // Load SuperBlock page.
        if ((m_pSuper = new SuperBlockPage()) == 0)
            return store_E_OutOfMemory;

        eErrCode = read (0, m_pSuper, SuperBlockPage::theSize);
        if (eErrCode == store_E_None)
        {
            // Verify SuperBlock page (with repair).
            eErrCode = m_pSuper->verify (*this);
        }
    }
    else
    {
        // Truncate to zero length.
        eErrCode = m_xLockBytes->setSize(0);
        if (eErrCode != store_E_None)
            return eErrCode;

        // Mark as not existing.
        eErrCode = store_E_NotExists;
    }

    if (eErrCode != store_E_None)
    {
        // Check reason.
        if (eErrCode != store_E_NotExists)
            return eErrCode;

        // Check mode.
        if (eAccessMode == store_AccessReadOnly)
            return store_E_NotExists;
        if (eAccessMode == store_AccessReadWrite)
            return store_E_NotExists;

        // Check PageSize.
        if ((STORE_MINIMUM_PAGESIZE > rnPageSize) || (rnPageSize > STORE_MAXIMUM_PAGESIZE))
            return store_E_InvalidParameter;
        rnPageSize = ((rnPageSize + STORE_MINIMUM_PAGESIZE - 1) & ~(STORE_MINIMUM_PAGESIZE - 1));

        // Create initial page (w/ SuperBlock).
        if ((m_pSuper = new(rnPageSize) SuperBlockPage(rnPageSize)) == 0)
            return store_E_OutOfMemory;
        eErrCode = m_pSuper->save (*this, rnPageSize);
    }
    if (eErrCode == store_E_None)
    {
        // Obtain page size.
        rnPageSize = store::ntohs(m_pSuper->m_aSuperOne.m_aDescr.m_nSize);

        // Create page allocator.
        eErrCode = m_xLockBytes->initialize (m_xAllocator, rnPageSize);
        if (eErrCode != store_E_None)
            return eErrCode;

        // Create page cache.
        eErrCode = PageCache_createInstance (m_xCache, rnPageSize);
    }
    return eErrCode;
}

/*
 * cleanup_Impl.
 * Internal: Precond: exclusive access.
 */
void OStorePageBIOS::cleanup_Impl()
{
    // Check referer count.
    if (m_ace_head.m_used > 0)
    {
        // Report remaining referer count.
        OSL_TRACE("store::PageBIOS::cleanup_Impl(): referer count: %d\n", m_ace_head.m_used);
        for (Ace * ace = m_ace_head.m_next; ace != &m_ace_head; ace = m_ace_head.m_next)
        {
            m_ace_head.m_used -= ace->m_used;
            AceCache::get().destroy (ace);
        }
        OSL_ENSURE(m_ace_head.m_used == 0, "store::PageBIOS::cleanup_Impl(): logic error");
    }

    // Release SuperBlock page.
    delete m_pSuper, m_pSuper = 0;

    // Release PageCache.
    m_xCache.clear();

    // Release PageAllocator.
    m_xAllocator.clear();

    // Release LockBytes.
    m_xLockBytes.clear();
}

/*
 * read.
 * Low Level: Precond: initialized, exclusive access.
 */
storeError OStorePageBIOS::read (
    sal_uInt32 nAddr, void *pData, sal_uInt32 nSize)
{
    // Check precond.
    if (!m_xLockBytes.is())
        return store_E_InvalidAccess;

    // Read Data.
    return m_xLockBytes->readAt (nAddr, pData, nSize);
}

/*
 * write.
 * Low Level: Precond: initialized, writeable, exclusive access.
 */
storeError OStorePageBIOS::write (
    sal_uInt32 nAddr, const void *pData, sal_uInt32 nSize)
{
    // Check precond.
    if (!m_xLockBytes.is())
        return store_E_InvalidAccess;
    if (!m_bWriteable)
        return store_E_AccessViolation;

    // Write Data.
    return m_xLockBytes->writeAt (nAddr, pData, nSize);
}

/*
 * acquirePage.
 * Precond: initialized.
 */
storeError OStorePageBIOS::acquirePage (
    const OStorePageDescriptor& rDescr, storeAccessMode eMode)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard (m_aMutex);

    // Check precond.
    if (!m_xLockBytes.is())
        return store_E_InvalidAccess;

    // Check access mode.
    if (!(m_bWriteable || (eMode == store_AccessReadOnly)))
        return store_E_AccessViolation;

    // Find access control list entry.
    Ace * ace = Ace::find (&m_ace_head, rDescr.m_nAddr);
    if (ace->m_addr == rDescr.m_nAddr)
    {
      // Acquire existing entry (with ShareDenyWrite).
      if (eMode == store_AccessReadOnly)
        ace->m_used += 1;
      else
        return store_E_AccessViolation;
    }
    else
    {
      // Insert new entry.
      Ace * entry = AceCache::get().create (rDescr.m_nAddr, 1);
      if (!entry)
        return store_E_OutOfMemory;
      Ace::insert (ace, entry);
    }

    // Increment total referer count and finish.
    m_ace_head.m_used += 1;
    return store_E_None;
}

/*
 * releasePage.
 * Precond: initialized.
 */
storeError OStorePageBIOS::releasePage (
    const OStorePageDescriptor& rDescr, storeAccessMode /* eMode */)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard (m_aMutex);

    // Check precond.
    if (!m_xLockBytes.is())
        return store_E_InvalidAccess;

    // Find access control list entry.
    Ace * ace = Ace::find (&m_ace_head, rDescr.m_nAddr);
    if (ace->m_addr != rDescr.m_nAddr)
      return store_E_NotExists;

    // Release existing entry.
    if (ace->m_used > 1)
      ace->m_used -= 1;
    else
      AceCache::get().destroy (ace);

    // Decrement total referer count and finish.
    m_ace_head.m_used -= 1;
    return store_E_None;
}

/*
 * getRefererCount.
 * Precond: none.
 */
sal_uInt32 OStorePageBIOS::getRefererCount (void)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard (m_aMutex);

    // Obtain total referer count.
    return m_ace_head.m_used;
}

/*
 * allocate.
 * Precond: initialized, writeable.
 */
storeError OStorePageBIOS::allocate (
    OStorePageObject& rPage, Allocation eAlloc)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard (m_aMutex);

    // Check precond.
    if (!m_xLockBytes.is())
        return store_E_InvalidAccess;
    if (!m_bWriteable)
        return store_E_AccessViolation;

    // Check allocation type.
    storeError eErrCode = store_E_None;
    if (eAlloc != ALLOCATE_EOF)
    {
        // Try freelist head.
        PageData aPageHead;
        eErrCode = m_pSuper->unusedHead (*this, aPageHead);
        if (eErrCode != store_E_None)
            return eErrCode;

        sal_uInt32 const nAddr = aPageHead.location();
        if (nAddr != STORE_PAGE_NULL)
        {
            // Save page.
            eErrCode = saveObjectAt_Impl (rPage, nAddr);
            if (eErrCode != store_E_None)
                return eErrCode;

            // Pop freelist head and finish.
            return m_pSuper->unusedPop (*this, aPageHead);
        }
    }

    // Allocate from EOF. Determine current size.
    sal_uInt32 nSize = STORE_PAGE_NULL;
    eErrCode = m_xLockBytes->getSize (nSize);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Save page at current EOF.
    return saveObjectAt_Impl (rPage, nSize);
}

/*
 * free.
 * Precond: initialized, writeable.
 */
storeError OStorePageBIOS::free (sal_uInt32 nAddr)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard (m_aMutex);

    // Check precond.
    if (!m_xLockBytes.is())
        return store_E_InvalidAccess;
    if (!m_bWriteable)
        return store_E_AccessViolation;

    // Invalidate cache.
    (void) m_xCache->removePageAt (nAddr);

    // Push onto freelist.
    return m_pSuper->unusedPush (*this, nAddr);
}

/*
 * loadObjectAt.
 * Precond: initialized, readable.
 */
storeError OStorePageBIOS::loadObjectAt (OStorePageObject & rPage, sal_uInt32 nAddr)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard (m_aMutex);

    // Check precond.
    if (!m_xLockBytes.is())
        return store_E_InvalidAccess;

    return loadObjectAt_Impl (rPage, nAddr);
}

/*
 * loadObjectAt_Impl.
 * Internal: Precond: initialized, readable, exclusive access.
 */
storeError OStorePageBIOS::loadObjectAt_Impl (OStorePageObject & rPage, sal_uInt32 nAddr)
{
    storeError eErrCode = m_xCache->lookupPageAt (rPage.get(), nAddr);
    if (eErrCode != store_E_NotExists)
        return eErrCode;

    // Read page.
    eErrCode = m_xLockBytes->readPageAt (rPage.get(), nAddr);
    if (eErrCode != store_E_None)
        return eErrCode;

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

    // Mark page as clean.
    rPage.clean();

    // Cache page.
    return m_xCache->insertPageAt (rPage.get(), nAddr);
}

/*
 * saveObjectAt.
 * Precond: initialized, writeable.
 */
storeError OStorePageBIOS::saveObjectAt (OStorePageObject & rPage, sal_uInt32 nAddr)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard (m_aMutex);

    // Check precond.
    if (!m_xLockBytes.is())
        return store_E_InvalidAccess;
    if (!m_bWriteable)
        return store_E_AccessViolation;

    // Save Page.
    return saveObjectAt_Impl (rPage, nAddr);
}

/*
 * saveObjectAt_Impl.
 * Internal: Precond: initialized, writeable, exclusive access.
 */
storeError OStorePageBIOS::saveObjectAt_Impl (OStorePageObject & rPage, sal_uInt32 nAddr)
{
    // Guard page (incl. set location).
    storeError eErrCode = rPage.guard (nAddr);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Write page.
    eErrCode = m_xLockBytes->writePageAt(rPage.get(), nAddr);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Mark page as clean.
    rPage.clean();

    // Cache page.
    return m_xCache->updatePageAt (rPage.get(), nAddr);
}

/*
 * close.
 * Precond: none.
 */
storeError OStorePageBIOS::close()
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard (m_aMutex);

    // Cleanup.
    cleanup_Impl();

    // Done.
    return store_E_None;
}

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

    // Check precond.
    if (!m_xLockBytes.is())
        return store_E_InvalidAccess;

    // Flush LockBytes and finish.
    return m_xLockBytes->flush();
}

/*
 * size.
 * Precond: initialized.
 */
storeError OStorePageBIOS::size (sal_uInt32 &rnSize)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard (m_aMutex);

    // Initialize [out] param.
    rnSize = 0;

    // Check precond.
    if (!m_xLockBytes.is())
        return store_E_InvalidAccess;

    // Obtain LockBytes size.
    return m_xLockBytes->getSize (rnSize);
}

/*
 * scanBegin.
 * Precond: initialized.
 */
storeError OStorePageBIOS::scanBegin (
    ScanContext &rCtx, sal_uInt32 nMagic)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard (m_aMutex);

    // Initialize [out] param.
    rCtx.m_aDescr = OStorePageDescriptor(0, 0, 0);
    rCtx.m_nSize  = 0;
    rCtx.m_nMagic = nMagic;

    // Check precond.
    if (!m_xLockBytes.is())
        return store_E_InvalidAccess;

    // Check SuperBlock page.
    storeError eErrCode = m_pSuper->verify (*this);
    if (eErrCode != store_E_None)
    {
        // Damaged. Determine page size (NYI).
        OSL_TRACE ("OStorePageBIOS::scanBegin(): damaged.\n");
        return eErrCode;
    }

    // Setup Context descriptor.
    rCtx.m_aDescr = m_pSuper->m_aSuperOne.m_aDescr;
    rCtx.m_aDescr.m_nSize = store::ntohs(rCtx.m_aDescr.m_nSize);
    rCtx.m_aDescr.m_nAddr = rCtx.m_aDescr.m_nSize;

    // Setup Context size.
    eErrCode = size (rCtx.m_nSize);
    if (eErrCode != store_E_None)
        rCtx.m_nSize = ((sal_uInt32)(~0));

    // Done.
    return store_E_None;
}

/*
 * scanNext.
 * Precond: initialized.
 */
storeError OStorePageBIOS::scanNext (
    ScanContext &rCtx, OStorePageObject &rPage)
{
    // Acquire exclusive access.
    osl::MutexGuard aGuard (m_aMutex);

    // Check precond.
    if (!m_xLockBytes.is())
        return store_E_InvalidAccess;

    // Setup PageHead.
    PageData aPageHead;

    // Check context.
    while (rCtx.isValid())
    {
        // Assign next location.
        sal_uInt32 nAddr = rCtx.m_aDescr.m_nAddr;
        rCtx.m_aDescr.m_nAddr += rCtx.m_aDescr.m_nSize;

        // Read PageHead.
        storeError eErrCode = read (nAddr, &aPageHead, PageData::theSize);
        if (eErrCode != store_E_None)
            continue;

        // Verify PageHead.
        eErrCode = aPageHead.verify (nAddr);
        if (eErrCode != store_E_None)
            continue;

        // Check PageHead Magic number.
        if (aPageHead.m_aGuard.m_nMagic != rCtx.m_nMagic)
            continue;

        // Check PageHead Unused link.
        if (aPageHead.m_aUnused.m_nAddr != STORE_PAGE_NULL)
            continue;

        // Load page.
        eErrCode = loadObjectAt_Impl (rPage, nAddr);
        if (eErrCode != store_E_None)
            continue;

        // Deliver page.
        return store_E_None;
    }

    // Done.
    return store_E_CantSeek;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
