1
0
Fork 0
libreoffice/sot/source/sdstor/stg.cxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

941 lines
24 KiB
C++

/* -*- 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/storinfo.hxx>
#include <osl/diagnose.h>
#include <osl/file.hxx>
#include <unotools/tempfile.hxx>
#include <tools/stream.hxx>
#include <tools/debug.hxx>
#include <sot/stg.hxx>
#include <utility>
#include "stgelem.hxx"
#include "stgdir.hxx"
#include "stgio.hxx"
#include "stgole.hxx"
static tools::Long nTmpCount = 0;
// The internal open mode is StreamMode::READ | StreamMode::TRUNC, which is silly
// by itself. It inhibits the checking of sharing modes and is used
// during CopyTo() and MoveTo() for opening a stream in read mode
// although it may be open in DENYALL mode
#define INTERNAL_MODE ( StreamMode::READ | StreamMode::TRUNC )
///////////////////////// class StorageBase
StorageBase::StorageBase()
: m_bAutoCommit( false )
{
m_nMode = StreamMode::READ;
m_nError = ERRCODE_NONE;
}
StorageBase::~StorageBase()
{
}
// The following three methods are declared as const, since they
// may be called from within a const method.
ErrCode StorageBase::GetError() const
{
const ErrCode n = m_nError;
m_nError = ERRCODE_NONE;
return n;
}
void StorageBase::SetError( ErrCode n ) const
{
if( !m_nError )
m_nError = n;
}
void StorageBase::ResetError() const
{
m_nError = ERRCODE_NONE;
}
OLEStorageBase::OLEStorageBase( StgIo* p, StgDirEntry* pe, StreamMode& nMode )
: nStreamMode( nMode ), pIo( p ), pEntry( pe )
{
if ( p )
p->IncRef();
if( pe )
pe->m_nRefCnt++;
}
OLEStorageBase::~OLEStorageBase()
{
if( pEntry )
{
DBG_ASSERT( pEntry->m_nRefCnt, "RefCount under 0" );
if( !--pEntry->m_nRefCnt )
{
if( pEntry->m_bZombie )
delete pEntry;
else
pEntry->Close();
}
pEntry = nullptr;
}
if( pIo && !pIo->DecRef() )
{
delete pIo;
pIo = nullptr;
}
}
// Validate the instance for I/O
bool OLEStorageBase::Validate_Impl( bool bWrite ) const
{
return pIo
&& pIo->m_pTOC
&& pEntry
&& !pEntry->m_bInvalid
&& ( !bWrite || !pEntry->m_bDirect || ( nStreamMode & StreamMode::WRITE ) );
}
bool OLEStorageBase::ValidateMode_Impl( StreamMode m, StgDirEntry const * p )
{
if( m == INTERNAL_MODE )
return true;
StreamMode nCurMode = ( p && p->m_nRefCnt ) ? p->m_nMode : StreamMode::SHARE_DENYALL;
if( ( m & StreamMode::READWRITE ) == StreamMode::READ )
{
// only SHARE_DENYWRITE or SHARE_DENYALL allowed
if( ( ( m & StreamMode::SHARE_DENYWRITE )
&& ( nCurMode & StreamMode::SHARE_DENYWRITE ) )
|| ( ( m & StreamMode::SHARE_DENYALL )
&& ( nCurMode & StreamMode::SHARE_DENYALL ) ) )
return true;
}
else
{
// only SHARE_DENYALL allowed
// storages open in r/o mode are OK, since only
// the commit may fail
if( ( m & StreamMode::SHARE_DENYALL )
&& ( nCurMode & StreamMode::SHARE_DENYALL ) )
return true;
}
return false;
}
//////////////////////// class StorageStream
StorageStream::StorageStream( StgIo* p, StgDirEntry* q, StreamMode m )
: OLEStorageBase( p, q, m_nMode ), nPos( 0 )
{
// The dir entry may be 0; this means that the stream is invalid.
if( q && p )
{
if( q->m_nRefCnt == 1 )
{
q->m_nMode = m;
q->OpenStream( *p );
}
}
else
m &= ~StreamMode::READWRITE;
m_nMode = m;
}
StorageStream::~StorageStream()
{
// Do an auto-commit if the entry is open in direct mode
if( m_bAutoCommit )
Commit();
if( pEntry && pEntry->m_nRefCnt && pEntry->m_bDirect && (m_nMode & StreamMode::WRITE) )
pEntry->Commit();
}
bool StorageStream::Equals( const BaseStorageStream& rStream ) const
{
const StorageStream* pOther = dynamic_cast<const StorageStream*>( &rStream );
return pOther && ( pOther->pEntry == pEntry );
}
sal_Int32 StorageStream::Read( void* pData, sal_Int32 nSize )
{
if( Validate() )
{
pEntry->Seek( nPos );
nSize = pEntry->Read( pData, nSize );
pIo->MoveError( *this );
nPos += nSize;
}
else
nSize = 0;
return nSize;
}
sal_Int32 StorageStream::Write( const void* pData, sal_Int32 nSize )
{
if( Validate( true ) )
{
pEntry->Seek( nPos );
nSize = pEntry->Write( pData, nSize );
pIo->MoveError( *this );
nPos += nSize;
}
else
nSize = 0;
return nSize;
}
sal_uInt64 StorageStream::Seek( sal_uInt64 n )
{
if( Validate() )
{
nPos = pEntry->Seek( n );
return nPos;
}
else
return n;
}
void StorageStream::Flush()
{
// Flushing means committing, since streams are never transacted
Commit();
}
bool StorageStream::SetSize( sal_uInt64 nNewSize )
{
if( Validate( true ) )
{
bool b = pEntry->SetSize( nNewSize );
pIo->MoveError( *this );
return b;
}
else
return false;
}
sal_uInt64 StorageStream::GetSize() const
{
if( Validate() )
return pEntry->GetSize();
return 0;
}
bool StorageStream::Commit()
{
if( !Validate() )
return false;
if( !( m_nMode & StreamMode::WRITE ) )
{
SetError( SVSTREAM_ACCESS_DENIED );
return false;
}
else
{
pEntry->Commit();
pIo->MoveError( *this );
return Good();
}
}
void StorageStream::CopyTo( BaseStorageStream* pDest )
{
if( !Validate() || !pDest || !pDest->Validate( true ) || Equals( *pDest ) )
return;
pEntry->Copy( *pDest );
pDest->Commit();
pIo->MoveError( *this );
SetError( pDest->GetError() );
}
bool StorageStream::Validate( bool bValidate ) const
{
bool bRet = Validate_Impl( bValidate );
if ( !bRet )
SetError( SVSTREAM_ACCESS_DENIED );
return bRet;
}
bool StorageStream::ValidateMode( StreamMode nMode ) const
{
bool bRet = ValidateMode_Impl( nMode );
if ( !bRet )
SetError( SVSTREAM_ACCESS_DENIED );
return bRet;
}
///////////////////////// class SvStorageInfo
SvStorageInfo::SvStorageInfo( const StgDirEntry& rE )
{
rE.m_aEntry.GetName( aName );
bStorage = rE.m_aEntry.GetType() == STG_STORAGE;
bStream = rE.m_aEntry.GetType() == STG_STREAM;
nSize = bStorage ? 0 : rE.m_aEntry.GetSize();
}
/////////////////////////// class Storage
bool Storage::IsStorageFile( const OUString & rFileName )
{
StgIo aIo;
if( aIo.Open( rFileName, StreamMode::STD_READ ) )
return aIo.Load();
return false;
}
bool Storage::IsStorageFile( SvStream* pStream )
{
bool bRet = false;
if ( pStream )
{
StgHeader aHdr;
sal_uInt64 nPos = pStream->Tell();
bRet = ( aHdr.Load( *pStream ) && aHdr.Check() );
// It's not a stream error if it is too small for an OLE storage header
if ( pStream->GetErrorCode() == ERRCODE_IO_CANTSEEK )
pStream->ResetError();
pStream->Seek( nPos );
}
return bRet;
}
// Open the storage file. If writing is permitted and the file is not
// a storage file, initialize it.
Storage::Storage( OUString aFile, StreamMode m, bool bDirect )
: OLEStorageBase( new StgIo, nullptr, m_nMode )
, aName(std::move( aFile )), bIsRoot( false )
{
bool bTemp = false;
if( aName.isEmpty() )
{
// no name = temporary name!
aName = utl::CreateTempName();
bTemp = true;
}
// the root storage creates the I/O system
m_nMode = m;
if( pIo->Open( aName, m ) )
{
Init( ( m & ( StreamMode::TRUNC | StreamMode::NOCREATE ) ) == StreamMode::TRUNC );
if( pEntry )
{
pEntry->m_bDirect = bDirect;
pEntry->m_nMode = m;
pEntry->m_bTemp = bTemp;
}
}
else
{
pIo->MoveError( *this );
pEntry = nullptr;
}
}
// Create a storage on a given stream.
Storage::Storage( SvStream& r, bool bDirect )
: OLEStorageBase( new StgIo, nullptr, m_nMode )
, bIsRoot( false )
{
m_nMode = StreamMode::READ;
if( r.IsWritable() )
m_nMode = StreamMode::READ | StreamMode::WRITE;
if( r.GetError() == ERRCODE_NONE )
{
pIo->SetStrm( &r, false );
sal_uInt64 nSize = r.TellEnd();
r.Seek( 0 );
// Initializing is OK if the stream is empty
Init( nSize == 0 );
if( pEntry )
{
pEntry->m_bDirect = bDirect;
pEntry->m_nMode = m_nMode;
}
pIo->MoveError( *this );
}
else
{
SetError( r.GetError() );
pEntry = nullptr;
}
}
Storage::Storage( UCBStorageStream& rStrm, bool bDirect )
: OLEStorageBase( new StgIo, nullptr, m_nMode ), bIsRoot( false )
{
m_nMode = StreamMode::READ;
if ( rStrm.GetError() != ERRCODE_NONE )
{
SetError( rStrm.GetError() );
pEntry = nullptr;
return;
}
SvStream* pStream = rStrm.GetModifySvStream();
if ( !pStream )
{
OSL_FAIL( "UCBStorageStream can not provide SvStream implementation!" );
SetError( SVSTREAM_GENERALERROR );
pEntry = nullptr;
return;
}
if( pStream->IsWritable() )
m_nMode = StreamMode::READ | StreamMode::WRITE;
pIo->SetStrm( &rStrm );
sal_uInt64 nSize = pStream->TellEnd();
pStream->Seek( 0 );
// Initializing is OK if the stream is empty
Init( nSize == 0 );
if( pEntry )
{
pEntry->m_bDirect = bDirect;
pEntry->m_nMode = m_nMode;
}
pIo->MoveError( *this );
}
// Perform common code for both ctors above.
void Storage::Init( bool bCreate )
{
pEntry = nullptr;
bool bHdrLoaded = false;
bIsRoot = true;
assert(pIo && "The pointer may not be empty at this point!");
if( pIo->Good() && pIo->GetStrm() )
{
sal_uInt64 nSize = pIo->GetStrm()->TellEnd();
pIo->GetStrm()->Seek( 0 );
if( nSize )
{
bHdrLoaded = pIo->Load();
if( !bHdrLoaded && !bCreate )
{
// File is not a storage and not empty; do not destroy!
SetError( SVSTREAM_FILEFORMAT_ERROR );
return;
}
}
}
// file is a storage, empty or should be overwritten
pIo->ResetError();
// we have to set up the data structures, since
// the file is empty
if( !bHdrLoaded )
pIo->Init();
if( pIo->Good() && pIo->m_pTOC )
{
pEntry = pIo->m_pTOC->GetRoot();
pEntry->m_nRefCnt++;
}
}
// Internal ctor
Storage::Storage( StgIo* p, StgDirEntry* q, StreamMode m )
: OLEStorageBase( p, q, m_nMode ), bIsRoot( false )
{
if( q )
q->m_aEntry.GetName( aName );
else
m &= ~StreamMode::READWRITE;
m_nMode = m;
if( q && q->m_nRefCnt == 1 )
q->m_nMode = m;
}
Storage::~Storage()
{
// Invalidate all open substorages
if( m_bAutoCommit )
Commit();
if( pEntry )
{
// Do an auto-commit if the entry is open in direct mode
if( pEntry->m_nRefCnt && pEntry->m_bDirect && (m_nMode & StreamMode::WRITE) )
Commit();
if( pEntry->m_nRefCnt == 1 )
pEntry->Invalidate(false);
}
// close the stream is root storage
if( bIsRoot )
pIo->Close();
// remove the file if temporary root storage
if( bIsRoot && pEntry && pEntry->m_bTemp )
{
osl::File::remove( GetName() );
}
}
const OUString& Storage::GetName() const
{
if( !bIsRoot && Validate() )
pEntry->m_aEntry.GetName( const_cast<Storage*>(this)->aName );
return aName;
}
// Fill in the info list for this storage
void Storage::FillInfoList( SvStorageInfoList* pList ) const
{
if( !(Validate() && pList) )
return;
StgIterator aIter( *pEntry );
StgDirEntry* p = aIter.First();
while( p )
{
if( !p->m_bInvalid )
{
SvStorageInfo aInfo( *p );
pList->push_back( aInfo );
}
p = aIter.Next();
}
}
// Open or create a substorage
BaseStorage* Storage::OpenUCBStorage( const OUString& rName, StreamMode m, bool bDirect )
{
OSL_FAIL("Not supported!");
return OpenStorage( rName, m, bDirect );
}
BaseStorage* Storage::OpenOLEStorage( const OUString& rName, StreamMode m, bool bDirect )
{
return OpenStorage( rName, m, bDirect );
}
BaseStorage* Storage::OpenStorage( const OUString& rName, StreamMode m, bool bDirect )
{
if( !Validate() || !ValidateMode( m ) )
return new Storage( pIo, nullptr, m );
if( bDirect && !pEntry->m_bDirect )
bDirect = false;
StgDirEntry* p = StgDirStrm::Find( *pEntry, rName );
if( !p )
{
if( !( m & StreamMode::NOCREATE ) )
{
bool bTemp = false;
// create a new storage
OUString aNewName = rName;
if( aNewName.isEmpty() )
{
aNewName = "Temp Stg " + OUString::number( ++nTmpCount );
bTemp = true;
}
p = pIo->m_pTOC->Create( *pEntry, aNewName, STG_STORAGE );
if( p )
p->m_bTemp = bTemp;
}
if( !p )
pIo->SetError( ( m & StreamMode::WRITE )
? SVSTREAM_CANNOT_MAKE : SVSTREAM_FILE_NOT_FOUND );
}
else if( !ValidateMode( m, p ) )
p = nullptr;
if( p && p->m_aEntry.GetType() != STG_STORAGE )
{
pIo->SetError( SVSTREAM_FILE_NOT_FOUND );
// coverity[overwrite_var] - ownership is not here, but with StgDirStrm
p = nullptr;
}
// Either direct or transacted mode is supported
if( p && pEntry->m_nRefCnt == 1 )
p->m_bDirect = bDirect;
// Don't check direct conflict if opening readonly
if( p && (m & StreamMode::WRITE ))
{
if( p->m_bDirect != bDirect )
SetError( SVSTREAM_ACCESS_DENIED );
}
Storage* pStg = new Storage( pIo, p, m );
pIo->MoveError( *pStg );
if( m & StreamMode::WRITE ) pStg->m_bAutoCommit = true;
return pStg;
}
// Open a stream
BaseStorageStream* Storage::OpenStream( const OUString& rName, StreamMode m, bool )
{
if( !Validate() || !ValidateMode( m ) )
return new StorageStream( pIo, nullptr, m );
StgDirEntry* p = StgDirStrm::Find( *pEntry, rName );
bool bTemp = false;
if( !p )
{
if( !( m & StreamMode::NOCREATE ) )
{
// create a new stream
// make a name if the stream is temporary (has no name)
OUString aNewName( rName );
if( aNewName.isEmpty() )
{
aNewName = "Temp Strm " + OUString::number( ++nTmpCount );
bTemp = true;
}
p = pIo->m_pTOC->Create( *pEntry, aNewName, STG_STREAM );
}
if( !p )
pIo->SetError( ( m & StreamMode::WRITE )
? SVSTREAM_CANNOT_MAKE : SVSTREAM_FILE_NOT_FOUND );
}
else if( !ValidateMode( m, p ) )
p = nullptr;
// coverity[Resource leak : FALSE] - "Create" method is called with STG_STREAM line 620,
// so we won't enter into this "if" block here.
if( p && p->m_aEntry.GetType() != STG_STREAM )
{
pIo->SetError( SVSTREAM_FILE_NOT_FOUND );
p = nullptr;
}
if( p )
{
p->m_bTemp = bTemp;
p->m_bDirect = pEntry->m_bDirect;
}
StorageStream* pStm = new StorageStream( pIo, p, m );
if( p && !p->m_bDirect )
pStm->SetAutoCommit( true );
pIo->MoveError( *pStm );
return pStm;
}
// Delete a stream or substorage by setting the temp bit.
void Storage::Remove( const OUString& rName )
{
if( !Validate( true ) )
return;
StgDirEntry* p = StgDirStrm::Find( *pEntry, rName );
if( p )
{
p->Invalidate( true );
}
else
{
SetError( SVSTREAM_FILE_NOT_FOUND );
}
}
// Copy one element
bool Storage::CopyTo( const OUString& rElem, BaseStorage* pDest, const OUString& rNew )
{
if( !Validate() || !pDest || !pDest->Validate( true ) )
return false;
StgDirEntry* pElem = StgDirStrm::Find( *pEntry, rElem );
if( pElem )
{
if( pElem->m_aEntry.GetType() == STG_STORAGE )
{
// copy the entire storage
tools::SvRef<BaseStorage> p1 = OpenStorage( rElem, INTERNAL_MODE );
tools::SvRef<BaseStorage> p2 = pDest->OpenOLEStorage( rNew, StreamMode::WRITE | StreamMode::SHARE_DENYALL, pEntry->m_bDirect );
if ( p2 )
{
ErrCode nTmpErr = p2->GetError();
if( !nTmpErr )
{
p2->SetClassId( p1->GetClassId() );
p1->CopyTo( *p2 );
SetError( p1->GetError() );
nTmpErr = p2->GetError();
if( !nTmpErr )
p2->Commit();
else
pDest->SetError( nTmpErr );
}
else
pDest->SetError( nTmpErr );
}
return Good() && pDest->Good();
}
else
{
// stream copy
tools::SvRef<BaseStorageStream> p1 = OpenStream( rElem, INTERNAL_MODE );
tools::SvRef<BaseStorageStream> p2 = pDest->OpenStream( rNew, StreamMode::WRITE | StreamMode::SHARE_DENYALL, pEntry->m_bDirect );
if ( p2 )
{
ErrCode nTmpErr = p2->GetError();
if( !nTmpErr )
{
p1->CopyTo( p2.get() );
SetError( p1->GetError() );
nTmpErr = p2->GetError();
if( !nTmpErr )
p2->Commit();
else
pDest->SetError( nTmpErr );
}
else
pDest->SetError( nTmpErr );
}
return Good() && pDest->Good();
}
}
SetError( SVSTREAM_FILE_NOT_FOUND );
return false;
}
bool Storage::CopyTo( BaseStorage& rDest ) const
{
if( !Validate() || !rDest.Validate( true ) || Equals( rDest ) )
{
SetError( SVSTREAM_ACCESS_DENIED );
return false;
}
Storage* pThis = const_cast<Storage*>(this);
rDest.SetClassId( GetClassId() );
rDest.SetDirty();
SvStorageInfoList aList;
FillInfoList( &aList );
bool bRes = true;
for( size_t i = 0; i < aList.size() && bRes; i++ )
{
SvStorageInfo& rInfo = aList[ i ];
bRes = pThis->CopyTo( rInfo.GetName(), &rDest, rInfo.GetName() );
}
if( !bRes )
SetError( rDest.GetError() );
return Good() && rDest.Good();
}
bool Storage::IsStorage( const OUString& rName ) const
{
if( Validate() )
{
StgDirEntry* p = StgDirStrm::Find( *pEntry, rName );
if( p )
return p->m_aEntry.GetType() == STG_STORAGE;
}
return false;
}
bool Storage::IsStream( const OUString& rName ) const
{
if( Validate() )
{
StgDirEntry* p = StgDirStrm::Find( *pEntry, rName );
if( p )
return p->m_aEntry.GetType() == STG_STREAM;
}
return false;
}
bool Storage::IsContained( const OUString& rName ) const
{
if( Validate() )
return StgDirStrm::Find( *pEntry, rName ) != nullptr;
else
return false;
}
// Commit all sub-elements within this storage. If this is
// the root, commit the FAT, the TOC and the header as well.
bool Storage::Commit()
{
bool bRes = true;
if( !Validate() )
return false;
if( !( m_nMode & StreamMode::WRITE ) )
{
SetError( SVSTREAM_ACCESS_DENIED );
return false;
}
else
{
// Also commit the sub-streams and Storages
StgIterator aIter( *pEntry );
for( StgDirEntry* p = aIter.First(); p && bRes; p = aIter.Next() )
bRes = p->Commit();
if( bRes && bIsRoot )
{
bRes = pEntry->Commit();
if( bRes )
bRes = pIo->CommitAll();
}
pIo->MoveError( *this );
}
return bRes;
}
bool Storage::Revert()
{
return true;
}
///////////////////////////// OLE Support
// Set the storage type
void Storage::SetClass( const SvGlobalName & rClass,
SotClipboardFormatId nOriginalClipFormat,
const OUString & rUserTypeName )
{
if( Validate( true ) )
{
// set the class name in the root entry
pEntry->m_aEntry.SetClassId( rClass.GetCLSID() );
pEntry->SetDirty();
// then create the streams
StgCompObjStream aCompObj( *this, true );
aCompObj.GetClsId() = rClass.GetCLSID();
aCompObj.GetCbFormat() = nOriginalClipFormat;
aCompObj.GetUserName() = rUserTypeName;
if( !aCompObj.Store() )
SetError( aCompObj.GetError() );
else
{
StgOleStream aOle(*this);
if( !aOle.Store() )
SetError( aOle.GetError() );
}
}
else
SetError( SVSTREAM_ACCESS_DENIED );
}
SvGlobalName Storage::GetClassName()
{
StgCompObjStream aCompObj( *this, false );
if( aCompObj.Load() )
return SvGlobalName( aCompObj.GetClsId() );
pIo->ResetError();
if ( pEntry )
return SvGlobalName( pEntry->m_aEntry.GetClassId() );
return SvGlobalName();
}
SotClipboardFormatId Storage::GetFormat()
{
StgCompObjStream aCompObj( *this, false );
if( aCompObj.Load() )
return aCompObj.GetCbFormat();
pIo->ResetError();
return SotClipboardFormatId::NONE;
}
OUString Storage::GetUserName()
{
StgCompObjStream aCompObj( *this, false );
if( aCompObj.Load() )
return aCompObj.GetUserName();
pIo->ResetError();
return OUString();
}
bool Storage::ValidateFAT()
{
FatError nErr = pIo->ValidateFATs();
return nErr == FatError::Ok;
}
void Storage::SetDirty()
{
if ( pEntry )
pEntry->SetDirty();
}
void Storage::SetClassId( const ClsId& rId )
{
if ( pEntry )
pEntry->m_aEntry.SetClassId( rId );
}
const ClsId& Storage::GetClassId() const
{
if ( pEntry )
return pEntry->m_aEntry.GetClassId();
static const ClsId aDummyId = {0,0,0,{0,0,0,0,0,0,0,0}};
return aDummyId;
}
bool Storage::Validate( bool bValidate ) const
{
bool bRet = Validate_Impl( bValidate );
if ( !bRet )
SetError( SVSTREAM_ACCESS_DENIED );
return bRet;
}
bool Storage::ValidateMode( StreamMode nMode ) const
{
bool bRet = ValidateMode_Impl( nMode );
if ( !bRet )
SetError( SVSTREAM_ACCESS_DENIED );
return bRet;
}
bool Storage::ValidateMode( StreamMode nMode, StgDirEntry const * p ) const
{
bool bRet = ValidateMode_Impl( nMode, p );
if ( !bRet )
SetError( SVSTREAM_ACCESS_DENIED );
return bRet;
}
bool Storage::Equals( const BaseStorage& rStorage ) const
{
const Storage* pOther = dynamic_cast<const Storage*>( &rStorage );
return pOther && ( pOther->pEntry == pEntry );
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */