    /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
    /*
     * This file is part of the LibreOffice project.
     *
     * This Source Code Form is subject to the terms of the Mozilla Public
     * License, v. 2.0. If a copy of the MPL was not distributed with this
     * file, You can obtain one at http://mozilla.org/MPL/2.0/.
     *
     * This file incorporates work covered by the following license notice:
     *
     *   Licensed to the Apache Software Foundation (ASF) under one or more
     *   contributor license agreements. See the NOTICE file distributed
     *   with this work for additional information regarding copyright
     *   ownership. The ASF licenses this file to you under the Apache
     *   License, Version 2.0 (the "License"); you may not use this file
     *   except in compliance with the License. You may obtain a copy of
     *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
     */

    #include <sot/stg.hxx>
    #include "stgelem.hxx"
    #include "stgcache.hxx"
    #include "stgstrms.hxx"
    #include "stgdir.hxx"
    #include "stgio.hxx"
    #include <o3tl/safeint.hxx>
    #include <rtl/instance.hxx>
    #include <sal/log.hxx>

    #include <memory>

    ///////////////////////////// class StgIo

    // This class holds the storage header and all internal streams.

    StgIo::StgIo() : StgCache()
    {
        m_pTOC      = nullptr;
        m_pDataFAT  = nullptr;
        m_pDataStrm = nullptr;
        m_pFAT      = nullptr;
        m_bCopied   = false;
    }

    StgIo::~StgIo()
    {
        delete m_pTOC;
        delete m_pDataFAT;
        delete m_pDataStrm;
        delete m_pFAT;
    }

    // Load the header. Do not set an error code if the header is invalid.

    bool StgIo::Load()
    {
        if( GetStrm() )
        {
            if( m_aHdr.Load( *this ) )
            {
                if( m_aHdr.Check() )
                    SetupStreams();
                else
                    return false;
            }
            else
                return false;
        }
        return Good();
    }

    // Set up an initial, empty storage

    bool StgIo::Init()
    {
        m_aHdr.Init();
        SetupStreams();
        return CommitAll();
    }

    void StgIo::SetupStreams()
    {
        delete m_pTOC;
        delete m_pDataFAT;
        delete m_pDataStrm;
        delete m_pFAT;
        m_pTOC      = nullptr;
        m_pDataFAT  = nullptr;
        m_pDataStrm = nullptr;
        m_pFAT      = nullptr;
        ResetError();

        short nPhysPageSize = 1 << m_aHdr.GetPageSize();
        SetPhysPageSize(nPhysPageSize);
        sal_Int32 nFatStrmSize;
        if (o3tl::checked_multiply<sal_Int32>(m_aHdr.GetFATSize(), nPhysPageSize, nFatStrmSize))
        {
            SAL_WARN("sot", "Error: " << m_aHdr.GetFATSize() << " * " << nPhysPageSize << " would overflow");
            SetError(SVSTREAM_FILEFORMAT_ERROR);
            m_pFAT = nullptr;
            m_pTOC = nullptr;
            return;
        }

        m_pFAT = new StgFATStrm(*this, nFatStrmSize);
        m_pTOC = new StgDirStrm(*this);
        if( !GetError() )
        {
            StgDirEntry* pRoot = m_pTOC->GetRoot();
            if( pRoot )
            {
                m_pDataFAT = new StgDataStrm( *this, m_aHdr.GetDataFATStart(), -1 );
                m_pDataStrm = new StgDataStrm( *this, *pRoot );
                m_pDataFAT->SetIncrement( 1 << m_aHdr.GetPageSize() );
                m_pDataStrm->SetIncrement( GetDataPageSize() );
                m_pDataStrm->SetEntry( *pRoot );
            }
            else
                SetError( SVSTREAM_FILEFORMAT_ERROR );
        }
    }

    // get the logical data page size

    short StgIo::GetDataPageSize() const
    {
        return 1 << m_aHdr.GetDataPageSize();
    }

    // Commit everything

    bool StgIo::CommitAll()
    {
        // Store the data (all streams and the TOC)
        if( m_pTOC && m_pTOC->Store() && m_pDataFAT )
        {
            if( Commit() )
            {
                m_aHdr.SetDataFATStart( m_pDataFAT->GetStart() );
                m_aHdr.SetDataFATSize( m_pDataFAT->GetPages() );
                m_aHdr.SetTOCStart( m_pTOC->GetStart() );
                if( m_aHdr.Store( *this ) )
                {
                    GetStrm()->Flush();
                    const ErrCode n = GetStrm()->GetError();
                    SetError( n );
    #ifdef DBG_UTIL
                    if( n==ERRCODE_NONE ) ValidateFATs();
    #endif
                    return n == ERRCODE_NONE;
                }
            }
        }
        SetError( SVSTREAM_WRITE_ERROR );
        return false;
    }

    class EasyFat
    {
        std::unique_ptr<sal_Int32[]> pFat;
        std::unique_ptr<bool[]> pFree;
        sal_Int32 nPages;
        sal_Int32 nPageSize;

    public:
        EasyFat( StgIo & rIo, StgStrm *pFatStream, sal_Int32 nPSize );

        sal_Int32 GetPageSize() const { return nPageSize; }

        FatError Mark( sal_Int32 nPage, sal_Int32 nCount, sal_Int32 nExpect );
        bool HasUnrefChains() const;
    };

    EasyFat::EasyFat( StgIo& rIo, StgStrm* pFatStream, sal_Int32 nPSize )
    {
        nPages = pFatStream->GetSize() >> 2;
        nPageSize = nPSize;
        pFat.reset( new sal_Int32[ nPages ] );
        pFree.reset( new bool[ nPages ] );

        rtl::Reference< StgPage > pPage;
        sal_Int32 nFatPageSize = (1 << rIo.m_aHdr.GetPageSize()) - 2;

        for( sal_Int32 nPage = 0; nPage < nPages; nPage++ )
        {
            if( ! (nPage % nFatPageSize) )
            {
                pFatStream->Pos2Page( nPage << 2 );
                sal_Int32 nPhysPage = pFatStream->GetPage();
                pPage = rIo.Get( nPhysPage, true );
            }

            pFat[ nPage ] = StgCache::GetFromPage( pPage, short( nPage % nFatPageSize ) );
            pFree[ nPage ] = true;
        }
    }

    bool EasyFat::HasUnrefChains() const
    {
        for( sal_Int32 nPage = 0; nPage < nPages; nPage++ )
        {
            if( pFree[ nPage ] && pFat[ nPage ] != -1 )
                return true;
        }
        return false;
    }

    FatError EasyFat::Mark( sal_Int32 nPage, sal_Int32 nCount, sal_Int32 nExpect )
    {
        if( nCount > 0 )
        {
            --nCount /= GetPageSize();
            nCount++;
        }

        sal_Int32 nCurPage = nPage;
        while( nCount != 0 )
        {
            if( nCurPage < 0 || nCurPage >= nPages )
                return FatError::OutOfBounds;
            pFree[ nCurPage ] = false;
            nCurPage = pFat[ nCurPage ];
            // stream too long
            if( nCurPage != nExpect && nCount == 1 )
                return FatError::WrongLength;
            // stream too short
            if( nCurPage == nExpect && nCount != 1 && nCount != -1 )
                return FatError::WrongLength;
            // last block for stream without length
            if( nCurPage == nExpect && nCount == -1 )
                nCount = 1;
            if( nCount != -1 )
                nCount--;
        }
        return FatError::Ok;
    }

    class Validator
    {
        FatError nError;

        EasyFat aSmallFat;
        EasyFat aFat;

        StgIo &rIo;

        FatError ValidateMasterFATs();
        FatError ValidateDirectoryEntries();
        FatError FindUnrefedChains() const;
        FatError MarkAll( StgDirEntry *pEntry );

    public:
        explicit Validator( StgIo &rIo );
        bool IsError() const { return nError != FatError::Ok; }
    };

    Validator::Validator( StgIo &rIoP )
        : aSmallFat( rIoP, rIoP.m_pDataFAT, 1 << rIoP.m_aHdr.GetDataPageSize() ),
          aFat( rIoP, rIoP.m_pFAT, 1 << rIoP.m_aHdr.GetPageSize() ),
          rIo( rIoP )
    {
        FatError nErr = nError = FatError::Ok;

        if( ( nErr = ValidateMasterFATs() ) != FatError::Ok )
            nError = nErr;
        else if(    ( nErr = ValidateDirectoryEntries() ) != FatError::Ok )
            nError = nErr;
        else if(    ( nErr = FindUnrefedChains()) != FatError::Ok )
            nError = nErr;
    }

    FatError Validator::ValidateMasterFATs()
    {
        sal_Int32 nCount = rIo.m_aHdr.GetFATSize();
        FatError nErr;
        if ( !rIo.m_pFAT )
            return FatError::InMemoryError;

        for( sal_Int32 i = 0; i < nCount; i++ )
        {
            if( ( nErr = aFat.Mark(rIo.m_pFAT->GetPage(i, false), aFat.GetPageSize(), -3 )) != FatError::Ok)
                return nErr;
        }
        if( rIo.m_aHdr.GetMasters() )
            if( ( nErr = aFat.Mark(rIo.m_aHdr.GetFATChain( ), aFat.GetPageSize(), -4 )) != FatError::Ok )
                return nErr;

        return FatError::Ok;
    }

    FatError Validator::MarkAll( StgDirEntry *pEntry )
    {
        if ( !pEntry )
            return FatError::InMemoryError;

        StgIterator aIter( *pEntry );
        FatError nErr = FatError::Ok;
        for( StgDirEntry* p = aIter.First(); p ; p = aIter.Next() )
        {
            if( p->m_aEntry.GetType() == STG_STORAGE )
            {
                nErr = MarkAll( p );
                if( nErr != FatError::Ok )
                    return nErr;
            }
            else
            {
                sal_Int32 nSize = p->m_aEntry.GetSize();
                if( nSize < rIo.m_aHdr.GetThreshold()  )
                    nErr = aSmallFat.Mark( p->m_aEntry.GetStartPage(),nSize, -2 );
                else
                    nErr = aFat.Mark( p->m_aEntry.GetStartPage(),nSize, -2 );
                if( nErr != FatError::Ok )
                    return nErr;
            }
        }
        return FatError::Ok;
    }

    FatError Validator::ValidateDirectoryEntries()
    {
        if ( !rIo.m_pTOC )
            return FatError::InMemoryError;

        // Normal DirEntries
        FatError nErr = MarkAll( rIo.m_pTOC->GetRoot() );
        if( nErr != FatError::Ok )
            return nErr;
        // Small Data
        nErr = aFat.Mark( rIo.m_pTOC->GetRoot()->m_aEntry.GetStartPage(),
                     rIo.m_pTOC->GetRoot()->m_aEntry.GetSize(), -2 );
        if( nErr != FatError::Ok )
            return nErr;
        // Small Data FAT
        nErr = aFat.Mark(
            rIo.m_aHdr.GetDataFATStart(),
            rIo.m_aHdr.GetDataFATSize() * aFat.GetPageSize(), -2 );
        if( nErr != FatError::Ok )
            return nErr;
        // TOC
        nErr = aFat.Mark(
            rIo.m_aHdr.GetTOCStart(), -1, -2 );
        return nErr;
    }

    FatError Validator::FindUnrefedChains() const
    {
        if( aSmallFat.HasUnrefChains() ||
            aFat.HasUnrefChains() )
            return FatError::UnrefChain;
        else
            return FatError::Ok;
    }

    namespace { struct ErrorLink : public rtl::Static<Link<StgLinkArg&,void>, ErrorLink > {}; }

    void StgIo::SetErrorLink( const Link<StgLinkArg&,void>& rLink )
    {
        ErrorLink::get() = rLink;
    }

    const Link<StgLinkArg&,void>& StgIo::GetErrorLink()
    {
        return ErrorLink::get();
    }

    FatError StgIo::ValidateFATs()
    {
        if( m_bFile )
        {
            Validator *pV = new Validator( *this );
            bool bRet1 = !pV->IsError(), bRet2 = true ;
            delete pV;

            SvFileStream *pFileStrm = static_cast<SvFileStream *>( GetStrm() );
            if ( !pFileStrm )
                return FatError::InMemoryError;

            StgIo aIo;
            if( aIo.Open( pFileStrm->GetFileName(),
                          StreamMode::READ | StreamMode::SHARE_DENYNONE) &&
                aIo.Load() )
            {
                pV = new Validator( aIo );
                bRet2 = !pV->IsError();
                delete pV;
            }

            FatError nErr;
            if( bRet1 != bRet2 )
                nErr = bRet1 ? FatError::OnFileError : FatError::InMemoryError;
            else nErr = bRet1 ? FatError::Ok : FatError::BothError;
            if( nErr != FatError::Ok && !m_bCopied )
            {
                StgLinkArg aArg;
                aArg.aFile = pFileStrm->GetFileName();
                ErrorLink::get().Call( aArg );
                m_bCopied = true;
            }
    //      DBG_ASSERT( nErr == FatError::Ok ,"Storage broken");
            return nErr;
        }
    //  OSL_FAIL("Do not validate (no FileStorage)");
        return FatError::Ok;
    }

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

↑ V567 The modification of the 'nCount' variable is unsequenced relative to another operation on the same variable. This may lead to undefined behavior.