|
|
//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1991 - 2000.
//
// File: UPDATE.CXX
//
// Contents: Content Index Update
//
// History: 19-Mar-92 AmyA Created.
//
//----------------------------------------------------------------------------
#include <pch.cxx>
#pragma hdrstop
#include <imprsnat.hxx>
#include <pathpars.hxx>
#include <cifrmcom.hxx>
#include <lcase.hxx>
#include "cicat.hxx"
#include "update.hxx"
//+---------------------------------------------------------------------------
//
// Class: CImpersonatedFindFirst
//
// Purpose: A class that is capable of repeatedly trying to do a find
// first until there is success or is not retryable any more.
// Changes impersonation between attempts.
//
// History: 7-18-96 srikants Created
//
//----------------------------------------------------------------------------
class CImpersonatedFindFirst: public PImpersonatedWorkItem {
public:
CImpersonatedFindFirst( const CFunnyPath & funnyPath, HANDLE & h ) : PImpersonatedWorkItem( funnyPath.GetActualPath() ), _funnyPath( funnyPath ), _h(h) {
}
BOOL OpenFindFirst( CImpersonateRemoteAccess & remoteAccess );
BOOL DoIt(); // virtual
private:
HANDLE & _h; const CFunnyPath & _funnyPath; };
//+---------------------------------------------------------------------------
//
// Member: CImpersonatedFindFirst::OpenFindFirst
//
// Synopsis: Opens the first first handle, impersonating as necessary.
//
// Arguments: [remoteAccess] -
//
// Returns: TRUE if successful; FALSE o/w
//
// History: 7-18-96 srikants Created
//
//----------------------------------------------------------------------------
BOOL CImpersonatedFindFirst::OpenFindFirst( CImpersonateRemoteAccess & remoteAccess ) { BOOL fSuccess = TRUE;
TRY { ImpersonateAndDoWork( remoteAccess ); } CATCH( CException, e ) { ciDebugOut(( DEB_ERROR, "OpenFindFirst (%ws) failed with error (0x%X)\n", _funnyPath.GetPath(), e.GetErrorCode() )); fSuccess = FALSE; } END_CATCH
return fSuccess; }
//+---------------------------------------------------------------------------
//
// Member: CImpersonatedFindFirst::DoIt
//
// Synopsis: The worker method that does the work in the context of
// impersonation.
//
// Returns: TRUE if successful; FALSE if should be retried; THROWS
// otherwise.
//
// History: 7-18-96 srikants Created
//
//----------------------------------------------------------------------------
BOOL CImpersonatedFindFirst::DoIt() { //
// Open directory
//
NTSTATUS Status = CiNtOpenNoThrow( _h, _funnyPath.GetPath(), FILE_LIST_DIRECTORY | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT );
if ( !NT_SUCCESS(Status) ) { if ( IsRetryableError( Status ) ) return FALSE; else { THROW( CException( Status ) ); } }
return TRUE; } //DoIt
//+---------------------------------------------------------------------------
//
// Member: CTraverse::CTraverse
//
// Synopsis: Constructor of the CTraverse class.
//
// Arguments: [fAbort] - A reference to an abort triggering variable.
//
// History: 3-15-96 srikants Split from CUpdate
//
//----------------------------------------------------------------------------
CTraverse::CTraverse( CiCat & cat, BOOL & fAbort, BOOL fProcessRoot ) : _cat( cat ), _fAbort(fAbort), _fProcessRoot(fProcessRoot), _cProcessed( 0 ), _xbBuf( FINDFIRST_SIZE ), _pCurEntry( 0 ), _status( STATUS_SUCCESS ) { }
//+---------------------------------------------------------------------------
//
// Member: CTraverse::DoIt
//
// Synopsis: Traverses the tree from the given root and invokes the
// "ProcessFile" method on all the files found. Also, before
// starting to traverse a directory, it calls the
// "IsEligibleForTraversal()" method on that.
//
// Arguments: [lcaseFunnyRootPath] - The path to traverse.
//
// History: 3-15-96 srikants Split from CUpdate constructor
//
// Notes:
//
//----------------------------------------------------------------------------
void CTraverse::DoIt( const CLowerFunnyPath & lcaseFunnyRootPath ) { Push( lcaseFunnyRootPath );
if ( _fProcessRoot ) { Win4Assert( 0 == _pCurEntry );
// Note this is a bit of a hack. Only the attributes field is valid.
_pCurEntry = (CDirEntry *)_xbBuf.GetPointer();
ULONG ulAttrib = 0;
if ( CImpersonateRemoteAccess::IsNetPath( lcaseFunnyRootPath.GetActualPath() ) ) { CImpersonateRemoteAccess remote( _cat.GetImpersonationTokenCache() ); CImpersonatedGetFileAttr getAttr( lcaseFunnyRootPath );
getAttr.DoWork( remote ); ulAttrib = getAttr.GetAttr(); } else { ulAttrib = GetFileAttributes( lcaseFunnyRootPath.GetPath() ); }
_pCurEntry->SetAttributes( ulAttrib );
//
// If we got an error value for file attributes, then substitute zero
// because we don't want the directory bit to be turned on
//
if ( INVALID_FILE_ATTRIBUTES == _pCurEntry->Attributes() ) _pCurEntry->SetAttributes( 0 );
if ( !ProcessFile( lcaseFunnyRootPath ) ) return; }
CImpersonateRemoteAccess remote( _cat.GetImpersonationTokenCache() );
for (;;) { XPtr<CLowerFunnyPath> xLowerFunnyPath;
if ( !Pop( xLowerFunnyPath ) ) break;
if ( !Traverse(xLowerFunnyPath, remote)) break; } } //DoIt
//+---------------------------------------------------------------------------
//
// Member: CTraverse::Traverse
//
// Synopsis: Traverses the specified directory.
//
// Arguments: [dir] -
//
// History: 3-15-96 srikants Split from CUpdate::Traverse
//
//----------------------------------------------------------------------------
BOOL CTraverse::Traverse (XPtr<CLowerFunnyPath> & xLowerFunnyPath, CImpersonateRemoteAccess & remote ) { if ( !IsEligibleForTraversal( xLowerFunnyPath.GetReference() ) ) { ciDebugOut (( DEB_ITRACE, "Traverse skipping %ws\n", xLowerFunnyPath->GetActualPath() )); return TRUE; }
TraversalIdle();
CFullPath fullPath( xLowerFunnyPath->GetActualPath(), xLowerFunnyPath->GetActualLength() );
ciDebugOut (( DEB_ITRACE, "Traversing %ws\n", fullPath.GetBuf()));
ULONG cSkip = 0;
HANDLE h;
if ( CImpersonateRemoteAccess::IsNetPath( xLowerFunnyPath->GetActualPath() ) ) { CImpersonatedFindFirst findFirst( fullPath.GetFunnyPath(), h );
if ( !findFirst.OpenFindFirst( remote ) ) return TRUE; } else { //
// Open directory
//
NTSTATUS Status = CiNtOpenNoThrow( h, xLowerFunnyPath->GetPath(), FILE_LIST_DIRECTORY | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT );
if ( !NT_SUCCESS(Status) ) return TRUE; }
SHandle xHandle(h);
//
// First enumeration
//
IO_STATUS_BLOCK IoStatus; NTSTATUS Status;
Status = NtQueryDirectoryFile( h, // File
0, // Event
0, // APC routine
0, // APC context
&IoStatus, // I/O Status
_xbBuf.GetPointer(), // Buffer
_xbBuf.SizeOf(), // Buffer Length
FileBothDirectoryInformation, 0, // Multiple entry
0, // Filename
1 ); // Restart scan
if ( NT_SUCCESS(Status) ) Status = IoStatus.Status;
//
// If there are no more files or we don't have access to any more files
// in this branch of the tree, just continue traversing. Otherwise throw.
//
if ( !NT_SUCCESS(Status) ) { if ( STATUS_NO_MORE_FILES == Status || STATUS_NO_SUCH_FILE == Status || STATUS_ACCESS_DENIED == Status ) return TRUE;
THROW( CException( Status ) ); }
_pCurEntry = (CDirEntry *)_xbBuf.GetPointer(); BOOL fMore = TRUE;
ULONG ulScanBackoff = _cat.GetRegParams()->GetScanBackoff();
do { //
// Sleep for a while to let other I/O get through if appropriate.
//
// For reference, with 105,000 files on a free build, here are the
// scan times ( for mod of 50 and sleep(200) * ulScanBackoff )
//
// ScanBackoff Time (min:sec)
// ----------- --------------
// 0 8:40
// 1 18:03
// 3 43:11
//
if ( 0 != ulScanBackoff ) { if ( ulScanBackoff > CI_SCAN_BACKOFF_MAX_RANGE ) { //
// These values are reserved for future use.
//
} else if ( ( _cProcessed % 50 ) == 0 ) { for ( unsigned i = 0; !_fAbort && i < ulScanBackoff; i++ ) Sleep( 100 );
//
// If we're running ahead of filtering, then let's take a break.
// What's the point in running up a big change log for no reason?
//
// Attempt to keep primary and secondary store pages in memory.
// The primary store keeps 1489 records per 64k page and the
// secondary by default keeps 140. By keeping the scan
// thread only a little ahead of the filter thread we
// thrash less.
//
unsigned cLoops = 0;
while ( !_fAbort ) { CI_STATE State; State.cbStruct = sizeof State;
SCODE sc = _cat.CiState( State );
if ( FAILED(sc) || ( (State.cDocuments - State.cSecQDocuments) < 200 && ( 0 == (State.eState & ( CI_STATE_HIGH_IO | CI_STATE_LOW_MEMORY | CI_STATE_USER_ACTIVE ) ) ) ) ) break;
//
// Only call TraverseIdle if filtering has been stalled
// for a long time -- every 60 seconds or so.
//
cLoops++;
if ( 0 == ( cLoops % 30 ) ) TraversalIdle( TRUE ); // TRUE --> Stalled (not scanning)
//
// Sleep for a few of seconds, checking for abort.
//
for ( unsigned i = 0; !_fAbort && i < 3; i++ ) Sleep( 100 ); } } }
_cProcessed++;
//
// Get out?
//
if ( _fAbort ) { ciDebugOut(( DEB_ITRACE, "Aborting scan in the middle\n" )); break; }
fullPath.MakePath( _pCurEntry->Filename(), _pCurEntry->FilenameSize() / sizeof(WCHAR) );
BOOL fSkipFile = FALSE;
// If it's a directory and not a reparse point, push it on the stack
if ( ( 0 != ( _pCurEntry->Attributes() & FILE_ATTRIBUTE_DIRECTORY ) ) && ( 0 == ( _pCurEntry->Attributes() & FILE_ATTRIBUTE_REPARSE_POINT ) ) ) { if ( _pCurEntry->Filename()[0] == L'.' && ( _pCurEntry->FilenameSize() == sizeof(WCHAR) || (_pCurEntry->Filename()[1] == L'.' && _pCurEntry->FilenameSize() == sizeof(WCHAR) * 2 ) ) ) { fSkipFile = TRUE; } else { // Neither "." nor ".." directories
Push( fullPath.GetFunnyPath() ); } }
if ( !fSkipFile ) if ( !ProcessFile( fullPath.GetFunnyPath() ) ) { _status = HRESULT_FROM_WIN32( GetLastError() ); return FALSE; }
_pCurEntry = _pCurEntry->Next();
if ( 0 == _pCurEntry ) { Status = NtQueryDirectoryFile( h, // File
0, // Event
0, // APC routine
0, // APC context
&IoStatus, // I/O Status
_xbBuf.GetPointer(), // Buffer
_xbBuf.SizeOf(), // Buffer Length
FileBothDirectoryInformation, 0, // Multiple entry
0, // Filename
0 ); // Continue scan
if ( NT_SUCCESS(Status) ) Status = IoStatus.Status;
if ( NT_SUCCESS( Status ) ) _pCurEntry = (CDirEntry *)_xbBuf.GetPointer(); else fMore = FALSE; } } while ( fMore );
if ( !_fAbort ) { if ( STATUS_NO_SUCH_FILE == Status ) Status = STATUS_NO_MORE_FILES;
_status = Status;
return STATUS_NO_MORE_FILES == _status; }
return FALSE; }
//+-------------------------------------------------------------------------
//
// Member: CUpdate::CUpdate, public
//
// Synopsis: Perform update of scope. All work done from constructor.
//
// Arguments: [cat] -- Catalog
// [lcaseFunnyRootPath] -- Root of scope
// [PartId] -- Partition id
// [fIncrem] -- TRUE if this is an incremental update
//
// History: 04-Jan-96 KyleP Added header
//
//--------------------------------------------------------------------------
CUpdate::CUpdate ( CiCat& cat, ICiManager & ciManager, const CLowerFunnyPath & lcaseFunnyRootPath, PARTITIONID PartId, BOOL fIncrem, BOOL fDoDeletions, BOOL & fAbort, BOOL fProcessRoot ) : CTraverse( cat, fAbort, fProcessRoot ), _ciManager(ciManager), _cDoc(0), _PartId(PartId), _fIncrem(fIncrem), _fDoDeletions(fDoDeletions) { ciDebugOut(( DEB_ITRACE, "CUpdate::CUpdate root: '%ws', fAbort: %d\n", lcaseFunnyRootPath.GetActualPath(), fAbort ));
BOOL fRootDeleted = FALSE;
//
// If the rootPath is a network path, it is possible that the connection
// is down. _cat.StartUpdate() is a fairly expensive operation because it
// involves scanning the whole property store.
// Before doing any further processing, just check if we can
// open the path and get some information on it.
//
// Also, check that the root of the scope exists. If not, delete the
// files in the scope
//
if ( CImpersonateRemoteAccess::IsNetPath( lcaseFunnyRootPath.GetActualPath() ) ) { CImpersonateRemoteAccess remote( _cat.GetImpersonationTokenCache() );
TRY { CImpersonatedGetAttr getAttr( lcaseFunnyRootPath ); getAttr.DoWork( remote ); } CATCH( CException, e ) { ciDebugOut(( DEB_WARN, "CUpdate::CUpdate(a) can't get attrinfo: %#x, for %ws\n", e.GetErrorCode(), lcaseFunnyRootPath.GetActualPath())); if ( HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ) != e.GetErrorCode() || HRESULT_FROM_WIN32( ERROR_PATH_NOT_FOUND ) != e.GetErrorCode()) RETHROW();
fRootDeleted = TRUE; } END_CATCH; } else { DWORD dw = GetFileAttributes( lcaseFunnyRootPath.GetPath() ); if ( 0xffffffff == dw ) { DWORD dwErr = GetLastError(); ciDebugOut(( DEB_WARN, "CUpdate::CUpdate(b) can't get attrinfo: %d, for %ws\n", dwErr, lcaseFunnyRootPath.GetPath())); if ( ERROR_FILE_NOT_FOUND == dwErr || ERROR_PATH_NOT_FOUND == dwErr ) fRootDeleted = TRUE; } }
_docList.SetPartId ( _PartId ); _cat.StartUpdate( &_timeLastUpd, lcaseFunnyRootPath.GetActualPath(), fDoDeletions, eScansArray );
// Make sure EndProcessing deletes all the files in the scope
if ( fRootDeleted ) { _status = STATUS_NO_MORE_FILES; return; }
//
// This is a bit of a kludge. If we're doing an incremental update, we
// will not *update* the root, but we need to mark it as seen so it
// won't get deleted.
//
if ( _fDoDeletions ) { WORKID wid = _cat.PathToWorkId( lcaseFunnyRootPath, eScansArray, fileIdInvalid );
//
// Workid is invalid for root, such as f:\ //
//NOTE: LokSetSeen is called by PathToWorkId
//if ( wid != widInvalid )
// _cat.Touch( wid, eScansArray );
}
# if CIDBG == 1
SYSTEMTIME time; RtlZeroMemory( &time, sizeof(time) ); FileTimeToSystemTime( &_timeLastUpd, &time );
TIME_ZONE_INFORMATION tzinfo; GetTimeZoneInformation( &tzinfo );
SYSTEMTIME timeLocal; SystemTimeToTzSpecificLocalTime( &tzinfo, &time, &timeLocal );
ciDebugOut(( DEB_ITRACE, "Begin update. Last update %4d/%02d/%02d %02d:%02d:%02d.%d\n", timeLocal.wYear, timeLocal.wMonth, timeLocal.wDay, timeLocal.wHour, timeLocal.wMinute, timeLocal.wSecond, timeLocal.wMilliseconds )); # endif
DoIt( lcaseFunnyRootPath ); } //CUpdate
//+-------------------------------------------------------------------------
//
// Member: CUpdate::EndProcessing, public
//
// Synopsis: Flush final updates to catalog.
//
// History: 04-Jan-96 KyleP Added header
//
//--------------------------------------------------------------------------
void CUpdate::EndProcessing() { ciDebugOut(( DEB_ITRACE, "CUpdate::EndProcessing, _fAbort: %d, _status %#x\n", _fAbort, _status )); if ( !_fAbort ) { if ( _cDoc != 0 ) { _docList.LokSetCount ( _cDoc ); _cat.AddDocuments( _docList ); _docList.LokClear(); _docList.SetPartId ( _PartId ); _cDoc = 0; }
if ( STATUS_NO_MORE_FILES == _status ) { _cat.EndUpdate( _fDoDeletions, eScansArray ); } else if ( STATUS_SUCCESS != _status ) { ciDebugOut(( DEB_ERROR, "Error %#x while traversing\n", _status )); THROW( CException( _status ) ); } } } //EndProcessing
//+---------------------------------------------------------------------------
//
// Member: CUpdate::ProcessFile
//
// Synopsis: Processes the given file and adds it to the list of changed
// documents if necessary.
//
// Arguments: [lcaseFunnyPath] - Path of the file to be processed.
//
// Returns: TRUE if successful; FALSE o/w
//
// History: 3-15-96 srikants Split from CUpdate::Traverse
//
//----------------------------------------------------------------------------
BOOL CUpdate::ProcessFile( const CLowerFunnyPath & lcaseFunnyPath ) { if (lcaseFunnyPath.IsRemote()) { // Need to impersonate only for the remote case
CImpersonateSystem impersonate;
return ProcessFileInternal(lcaseFunnyPath); } else return ProcessFileInternal(lcaseFunnyPath); } //ProcessFile
//+---------------------------------------------------------------------------
//
// Member: CUpdate::ProcessFileInternal
//
// Synopsis: Processes the given file and adds it to the list of changed
// documents if necessary.
//
// Arguments: [lcaseFunnyPath] - Path of the file to be processed.
//
// Returns: TRUE if successful; FALSE o/w
//
// History: 3-15-96 srikants Split from CUpdate::Traverse
//
//----------------------------------------------------------------------------
BOOL CUpdate::ProcessFileInternal( const CLowerFunnyPath & lcaseFunnyPath ) { //
// Add path without committing it
//
FILETIME ftLastSeen = { 0,0 }; BOOL fNew; WORKID wid = _cat.PathToWorkId( lcaseFunnyPath, TRUE, fNew, _fIncrem ? &ftLastSeen : 0, eScansArray, fileIdInvalid );
if (wid == widInvalid) return TRUE;
//
// Decide if file has been updated.
//
LONG result = -1;
if ( _fIncrem && !fNew && !CiCat::IsMaxTime( ftLastSeen ) ) { //
// Use the bigger of the last seen time and time of last update
// to compare the current time of the file.
//
if ( ( ( 0 != ftLastSeen.dwLowDateTime ) || ( 0 != ftLastSeen.dwHighDateTime ) ) && ( CompareFileTime( &_timeLastUpd, &ftLastSeen ) > 0 ) ) { ftLastSeen = _timeLastUpd; }
//
// On FAT volumes, ChangeTime is always 0. On NTFS, ChangeTime
// can be more recent than ModifyTime (alias LastWriteTime) if the
// change was to the ACLs of a file, in which case we need to
// honor the change.
//
FILETIME ftLastDelta; if ( 0 == _pCurEntry->ChangeTime() ) ftLastDelta = _pCurEntry->ModifyTimeAsFILETIME(); else ftLastDelta = _pCurEntry->ChangeTimeAsFILETIME();
result = CompareFileTime( &ftLastSeen, &ftLastDelta ); }
if (result < 0) // The file is newer than the catalog
{ ciDebugOut(( DEB_CAT, "Update %ws\n", lcaseFunnyPath.GetActualPath() ));
_cat.WriteFileAttributes( wid, _pCurEntry->Attributes() ); Add ( wid ); }
return TRUE; } //ProcessFileInternal
//+---------------------------------------------------------------------------
//
// Member: CUpdate::IsEligibleForTraversal
//
// Synopsis: Checks to see if the current directory is eligible for
// traversal.
//
// Arguments: [lcaseFunnyDir] -
//
// History: 3-15-96 srikants Created
//
//----------------------------------------------------------------------------
BOOL CUpdate::IsEligibleForTraversal( const CLowerFunnyPath & lcaseFunnyDir ) const { return _cat.IsEligibleForFiltering( lcaseFunnyDir ); }
//+-------------------------------------------------------------------------
//
// Member: CUpdate::Add, public
//
// Synopsis: Add wid for update to doclist
//
// Arguments: [wid] -- Workid
//
// History: 04-Jan-96 KyleP Added header
//
//--------------------------------------------------------------------------
void CUpdate::Add( WORKID wid ) { _cat.Touch(wid, eScansArray); USN usn = 0;
_docList.Set ( _cDoc, wid, usn, CI_VOLID_USN_NOT_ENABLED ); _cDoc++;
if (_cDoc == CI_MAX_DOCS_IN_WORDLIST) { _docList.LokSetCount ( _cDoc );
_cat.AddDocuments( _docList );
_docList.LokClear(); _docList.SetPartId ( _PartId ); _cDoc = 0; } }
//+---------------------------------------------------------------------------
//
// Member: CRenameDir::CRenameDir
//
// Synopsis: Constructor
//
// Arguments: [strings] -- Strings class
// [lowerFunnyDirOldName] -- Old directory
// [lowerFunnyDirNewName] -- Renamed directory
// [fAbort] -- Set to TRUE for aborting
// [volumeId] -- Volume id
//
// History: 20-Mar-96 SitaramR Created
//
//----------------------------------------------------------------------------
CRenameDir::CRenameDir( CiCat & cicat, const CLowerFunnyPath & lowerFunnyDirOldName, const CLowerFunnyPath & lowerFunnyDirNewName, BOOL & fAbort, VOLUMEID volumeId ) : CTraverse( cicat, fAbort, TRUE ), _cicat(cicat), _volumeId(volumeId), _lowerFunnyDirOldName( lowerFunnyDirOldName ) { _lowerFunnyDirOldName.AppendBackSlash(); _uLenDirOldName = _lowerFunnyDirOldName.GetActualLength(); _uLenDirNewName = lowerFunnyDirNewName.GetActualLength();
//
// Recursive traversal of renamed dir (include the renamed dir also)
//
DoIt( lowerFunnyDirNewName ); }
//+---------------------------------------------------------------------------
//
// Member: CRenameDir::ProcessFile
//
// Synopsis: Update indexes to reflect new file name
//
// Arguments: [lcaseFunnyFileNewName] - File name
//
// History: 20-Mar-96 SitaramR Created
//
//----------------------------------------------------------------------------
BOOL CRenameDir::ProcessFile( const CLowerFunnyPath & lcaseFunnyFileNewName ) { unsigned uLenFileNewName = lcaseFunnyFileNewName.GetActualLength(); Win4Assert( uLenFileNewName >= _uLenDirNewName );
_lowerFunnyDirOldName.Truncate( _uLenDirOldName ); _lowerFunnyDirOldName.AppendBackSlash();
unsigned posNewFileNameStart = _uLenDirNewName;
if ( L'\\' == lcaseFunnyFileNewName.GetActualPath()[posNewFileNameStart] ) { posNewFileNameStart++; } _lowerFunnyDirOldName.AppendPath( lcaseFunnyFileNewName.GetActualPath() + posNewFileNameStart, uLenFileNewName - posNewFileNameStart );
_lowerFunnyDirOldName.RemoveBackSlash();
CLowerFunnyPath lcaseFunnyFileNewName2( lcaseFunnyFileNewName ); lcaseFunnyFileNewName2.RemoveBackSlash();
_cicat.RenameFile( _lowerFunnyDirOldName, lcaseFunnyFileNewName2, _pCurEntry->Attributes(), _volumeId, fileIdInvalid );
return TRUE; }
|