Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

942 lines
27 KiB

//+-------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1996 - 2000.
//
// File: main.cxx
//
// Contents: Fixes a catalog after propagating from one machine to another.
//
// History: 12 Jan 2000 dlee Created from propdump
//
//--------------------------------------------------------------------------
#include <pch.cxx>
#pragma hdrstop
#include <proprec.hxx>
#include <propdesc.hxx>
#include "mmfile.hxx"
DECLARE_INFOLEVEL(ci)
unsigned const SixtyFourK = 1024 * 64;
//#define LOG
CCoTaskAllocator CoTaskAllocator; // exported data definition
void * CCoTaskAllocator::Allocate(ULONG cbSize)
{
return CoTaskMemAlloc( cbSize );
}
void CCoTaskAllocator::Free(void *pv)
{
CoTaskMemFree(pv);
}
void Usage()
{
printf( "Usage: PropCi directory\n");
printf( " directory specifies the directory where the catalog exists.\n");
printf( "\n" );
printf( "This application converts file indexes in the catalog specified.\n" );
exit( 1 );
}
WCHAR const * wcsistr( WCHAR const * wcsString, WCHAR const * wcsPattern )
{
if ( (wcsPattern == 0) || (*wcsPattern == 0) )
return wcsString;
ULONG cwcPattern = wcslen(wcsPattern);
while ( *wcsString != 0 )
{
while ( (*wcsString != 0) &&
(towupper(*wcsString) != towupper(*wcsPattern)) )
wcsString++;
if ( 0 == *wcsString )
return 0;
if ( _wcsnicmp( wcsString, wcsPattern, cwcPattern) == 0 )
return wcsString;
wcsString++;
}
return 0;
} //wcsistr
void AppendBackslash( WCHAR *p )
{
int x = wcslen( p );
if ( 0 != x && L'\\' != p[x-1] )
{
p[x] = L'\\';
p[x+1] = 0;
}
} //AppendBackslash
void FindFieldRec(
WCHAR const * pwcFile,
PROPID pid,
CPropDesc & prop,
ULONG & cTotal,
ULONG & cFixed,
ULONG & culFixed )
{
cTotal = 0;
cFixed = 0;
culFixed = 0;
HANDLE h = CreateFile( pwcFile,
GENERIC_READ,
FILE_SHARE_READ,
0,
OPEN_EXISTING,
0,
0 );
if ( INVALID_HANDLE_VALUE == h )
{
printf( "Can't open file %ws. Error %u\n", pwcFile, GetLastError() );
exit( 1 );
}
ULONG cbRead;
static BYTE abTemp[SixtyFourK];
if ( ReadFile( h,
abTemp,
sizeof abTemp ,
&cbRead,
0 ) )
{
// Loop through records
BOOL fFound = FALSE;
CPropDesc * pPropDesc = (CPropDesc *)abTemp;
for ( unsigned i = 0;
i < cbRead/(sizeof(CPropDesc) + sizeof(ULONG));
i++, pPropDesc = (CPropDesc *)(((BYTE *)pPropDesc) + sizeof(CPropDesc) + sizeof(ULONG)) )
{
if ( 0 != pPropDesc->Pid() )
{
if ( pPropDesc->Pid() == pid )
{
memcpy( &prop, pPropDesc, sizeof prop );
fFound = TRUE;
}
cTotal++;
if ( pPropDesc->Offset() != -1 )
{
cFixed++;
culFixed += (pPropDesc->Size() / sizeof(DWORD));
}
}
}
if ( !fFound )
{
printf( "can't find pid %#x\n", pid );
exit( 1 );
}
}
else
{
printf( "Can't read file %ws. Error %u\n", pwcFile, GetLastError() );
exit( 1 );
}
#ifdef LOG
printf( "pid %d, total %d, fixed %d\n", pid, cTotal, cFixed );
printf( " pid %d, vt %d, ostart %d, cbmax %d, ordinal %d, mask %d, rec %d, fmodify %d\n",
prop.Pid(),
prop.Type(),
prop.Offset(),
prop.Size(),
prop.Ordinal(),
prop.Mask(),
prop.Record(),
prop.Modifiable() );
#endif
CloseHandle( h );
} //FindFieldRec
NTSTATUS CiNtOpenNoThrow(
HANDLE & h,
WCHAR const * pwcsPath,
ACCESS_MASK DesiredAccess,
ULONG ShareAccess,
ULONG OpenOptions )
{
h = INVALID_HANDLE_VALUE;
UNICODE_STRING uScope;
if ( !RtlDosPathNameToNtPathName_U( pwcsPath,
&uScope,
0,
0 ) )
return STATUS_INSUFFICIENT_RESOURCES;
//
// Open the file
//
OBJECT_ATTRIBUTES ObjectAttr;
InitializeObjectAttributes( &ObjectAttr, // Structure
&uScope, // Name
OBJ_CASE_INSENSITIVE, // Attributes
0, // Root
0 ); // Security
IO_STATUS_BLOCK IoStatus;
NTSTATUS Status = NtOpenFile( &h, // Handle
DesiredAccess, // Access
&ObjectAttr, // Object Attributes
&IoStatus, // I/O Status block
ShareAccess, // Shared access
OpenOptions ); // Flags
RtlFreeHeap( RtlProcessHeap(), 0, uScope.Buffer );
return Status;
} //CiNtOpenNoThrow
FILEID GetFileId( WCHAR * pwcPath )
{
if ( 2 == wcslen( pwcPath ) )
wcscat( pwcPath, L"\\" );
HANDLE h;
NTSTATUS status = CiNtOpenNoThrow( h,
pwcPath,
FILE_READ_ATTRIBUTES | SYNCHRONIZE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
0 );
if ( FAILED( status ) )
{
printf( "Can't open file %ws to get fileid. Error %#x\n", pwcPath, status );
return 0;
}
FILE_INTERNAL_INFORMATION fii;
IO_STATUS_BLOCK IoStatus;
status = NtQueryInformationFile( h,
&IoStatus,
&fii,
sizeof fii,
FileInternalInformation );
NtClose( h );
if ( NT_SUCCESS( status ) )
status = IoStatus.Status;
if ( NT_SUCCESS( status ) )
return fii.IndexNumber.QuadPart;
return 0;
} //GetFileId
void GetRecordInformation(
WCHAR const * pwcFile,
ULONG & cRecPerBuf,
ULONG & cbRec )
{
cRecPerBuf = 0;
cbRec = 0;
HANDLE h = CreateFile( pwcFile,
GENERIC_READ,
FILE_SHARE_READ,
0,
OPEN_EXISTING,
0,
0 );
if ( INVALID_HANDLE_VALUE == h )
{
printf( "Can't open file %ws. Error %u\n", pwcFile, GetLastError() );
exit( 1 );
}
ULONG cbRead;
static BYTE abTemp[SixtyFourK];
if ( ReadFile( h,
abTemp,
sizeof(abTemp),
&cbRead,
0 ) )
{
//
// Determine record size
//
if ( abTemp[0] != 0 || abTemp[1] != 0 )
{
printf( "Record 0 not blank. File corrupt!\n" );
exit( 1 );
}
// First record should be all empty and only the first
// record should be so. So counting all leading zeros gives us
// the size of the record.
for ( unsigned i = 0; i < cbRead && abTemp[i] == 0; i++ )
continue;
if ( i == cbRead )
{
printf( "First %uK segment all zero!. File corrupt!\n", sizeof(abTemp)/1024 );
exit( 1 );
}
switch ( *(USHORT *)&abTemp[i] )
{
case 0x5555:
case 0xAAAA:
case 0xBBBB:
case 0xCCCC:
case 0xDDDD:
cbRec = i;
if ( cbRec % 4 != 0 )
{
printf( "Record size (%u bytes) not DWORD aligned!\n\n", cbRec );
exit( 1 );
}
cRecPerBuf = sizeof(abTemp) / cbRec;
break;
default:
printf( "First non-zero byte is not a proper signature (%u)!\n", *(SHORT *)&abTemp[i] );
exit( 1 );
}
}
else
{
printf( "can't read from file %ws, error %d\n", pwcFile, GetLastError() );
exit( 1 );
}
#ifdef LOG
printf( "cRecPerBuf %d, cbRec %d\n", cRecPerBuf, cbRec );
#endif
CloseHandle( h );
} //GetRecordInformation
void PatchFileIDs(
WCHAR const * pwcDir,
WCHAR const * pwcPri,
WCHAR const * pwcSec )
{
//
// First, read the property specifications for secondary wid, fileindex,
// lastseen, and path. The first 3 are in the primary and the last in
// the secondary.
//
WCHAR awcPriProp[ MAX_PATH ];
wcscpy( awcPriProp, pwcDir );
wcscat( awcPriProp, L"CIP10000.001" );
ULONG cPriTotal, cPriFixed, culPriFixed;
CPropDesc SecWidPtrFieldRec;
FindFieldRec( awcPriProp,
pidSecondaryStorage,
SecWidPtrFieldRec,
cPriTotal,
cPriFixed,
culPriFixed );
CPropDesc FileIdFieldRec;
FindFieldRec( awcPriProp,
pidFileIndex,
FileIdFieldRec,
cPriTotal,
cPriFixed,
culPriFixed );
CPropDesc LastSeenFieldRec;
FindFieldRec( awcPriProp,
pidLastSeenTime,
LastSeenFieldRec,
cPriTotal,
cPriFixed,
culPriFixed );
WCHAR awcSecProp[ MAX_PATH ];
wcscpy( awcSecProp, pwcDir );
wcscat( awcSecProp, L"CIP20000.001" );
ULONG cSecTotal, cSecFixed, culSecFixed;
CPropDesc PathFieldRec;
FindFieldRec( awcSecProp,
pidPath,
PathFieldRec,
cSecTotal,
cSecFixed,
culSecFixed );
//
// Get information about the number and size of records.
//
ULONG cPriRecPerBuf, cbPriRec;
GetRecordInformation( pwcPri, cPriRecPerBuf, cbPriRec );
ULONG cSecRecPerBuf, cbSecRec;
GetRecordInformation( pwcSec, cSecRecPerBuf, cbSecRec );
//
// Walk the property store, get the secondary wid, read the path from
// the secondary store, lookup the fileid, and write the fileid back
// info the primary store.
//
CMMFile pri( pwcPri, TRUE );
if ( !pri.Ok() )
{
printf( "can't map primary\n" );
exit( 1 );
}
BYTE * pb = (BYTE *) pri.GetMapping();
BYTE * pbBase = pb;
CMMFile sec( pwcSec, TRUE );
if ( !sec.Ok() )
{
printf( "can't map secondary\n" );
exit( 1 );
}
BYTE * pbSecBase = (BYTE *) sec.GetMapping();
FILETIME ftNow;
GetSystemTimeAsFileTime( &ftNow );
#ifdef LOG
printf( "pb %#x, size %d\n", pb, pri.FileSize() );
#endif
ULONG iRec = 0, iRecTotal = 0;
do
{
#ifdef LOG
printf( "record %d\n", iRecTotal );
#endif
// If we're at the end of a 64k page, go on to the next one.
if ( iRec == cPriRecPerBuf )
{
iRec = 0;
pb += 65536;
}
COnDiskPropertyRecord * pRec = new( iRec, pb, cbPriRec/4 ) COnDiskPropertyRecord;
if ( (BYTE *) pRec >= ( pbBase + pri.FileSize() ) )
break;
if ( pRec->IsTopLevel() )
{
PROPVARIANT var;
static BYTE abExtra[MAX_PATH * 5];
unsigned cbExtra = sizeof(abExtra);
pRec->ReadFixed( SecWidPtrFieldRec.Ordinal(),
SecWidPtrFieldRec.Mask(),
SecWidPtrFieldRec.Offset(),
cPriTotal,
SecWidPtrFieldRec.Type(),
var,
abExtra,
&cbExtra,
*((PStorage *)0) );
if ( VT_UI4 != var.vt )
{
printf( "failure: secondary wid wasn't a UI4\n" );
exit( 1 );
}
#ifdef LOG
printf( "secondary wid %d\n", var.ulVal );
#endif
ULONG iTargetSection = var.ulVal/cSecRecPerBuf;
BYTE * pbSec = pbSecBase + ( iTargetSection * SixtyFourK );
// Get the secondary store record
COnDiskPropertyRecord * pRec2 = new( var.ulVal % cSecRecPerBuf, pbSec, cbSecRec/4 ) COnDiskPropertyRecord;
// Read the path
cbExtra = sizeof abExtra;
#ifdef LOG
printf( "pRec2: %#x\n", pRec2 );
#endif
var.vt = VT_EMPTY;
if ( !pRec2->ReadVariable( PathFieldRec.Ordinal(),
PathFieldRec.Mask(),
culSecFixed,
cSecTotal,
cSecFixed,
var,
abExtra,
&cbExtra ) )
{
// It's in an overflow record
BOOL fOk;
do
{
//
// Check for existing overflow block.
//
WORKID widOverflow = pRec2->OverflowBlock();
if ( 0 == widOverflow )
break;
iTargetSection = widOverflow / cSecRecPerBuf;
pbSec = pbSecBase + ( iTargetSection * SixtyFourK );
pRec2 = new( widOverflow % cSecRecPerBuf, pbSec, cbSecRec/4 ) COnDiskPropertyRecord;
#ifdef LOG
printf( "overflow pRec2: %#x\n", pRec2 );
#endif
ULONG Ordinal = PathFieldRec.Ordinal() - cSecFixed;
DWORD Mask = (1 << ((Ordinal % 16) * 2) );
fOk = pRec2->ReadVariable( Ordinal, // Ordinal (assuming 0 fixed)
Mask, // Mask (assuming 0 fixed)
0, // Fixed properties
cSecTotal - cSecFixed,
0, // Count of fixed properties
var,
abExtra,
&cbExtra );
} while ( !fOk );
}
if ( VT_LPWSTR == var.vt )
{
// Get and set the fileindex for this volume
FILEID fid = GetFileId( var.pwszVal );
PROPVARIANT varId;
varId.vt = VT_UI8;
varId.hVal.QuadPart = fid;
pRec->WriteFixed( FileIdFieldRec.Ordinal(),
FileIdFieldRec.Mask(),
FileIdFieldRec.Offset(),
FileIdFieldRec.Type(),
cPriTotal,
varId );
// Set the last seen time so we don't reindex the file
PROPVARIANT varLS;
varLS.vt = VT_FILETIME;
varLS.filetime = ftNow;
pRec->WriteFixed( LastSeenFieldRec.Ordinal(),
LastSeenFieldRec.Mask(),
LastSeenFieldRec.Offset(),
LastSeenFieldRec.Type(),
cPriTotal,
varLS );
#ifdef LOG
printf( "fileid %#I64x, %ws\n", fid, var.pwszVal );
#endif
}
}
if ( pRec->IsValidType() )
{
iRec += pRec->CountRecords();
iRecTotal += pRec->CountRecords();
}
else
{
iRec++;
iRecTotal++;
}
} while ( TRUE );
pri.Flush();
} //PatchFileIDs
void GetNtVolumeInformation(
ULONG ulVolumeId,
ULONGLONG & ullVolumeCreationTime,
ULONG & ulVolumeSerialNumber,
ULONGLONG & ullJournalId )
{
ullVolumeCreationTime = 0;
ulVolumeSerialNumber = 0;
ullJournalId = 0;
WCHAR wszVolumePath[] = L"\\\\.\\a:";
wszVolumePath[4] = (WCHAR) ulVolumeId;
FILE_FS_VOLUME_INFORMATION VolumeInfo;
VolumeInfo.VolumeCreationTime.QuadPart = 0;
VolumeInfo.VolumeSerialNumber = 0;
HANDLE hVolume = CreateFile( wszVolumePath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
0,
0 );
if ( INVALID_HANDLE_VALUE == hVolume )
{
printf( "failure: can't open volume %ws\n", wszVolumePath );
exit( 1 );
}
IO_STATUS_BLOCK iosb;
RtlZeroMemory( &VolumeInfo, sizeof VolumeInfo );
NtQueryVolumeInformationFile( hVolume,
&iosb,
&VolumeInfo,
sizeof(VolumeInfo),
FileFsVolumeInformation );
//
// This call will only succeed on NTFS NT5 w/ USN Journal enabled.
//
USN_JOURNAL_DATA UsnJournalInfo;
RtlZeroMemory( &UsnJournalInfo, sizeof UsnJournalInfo );
NTSTATUS Status = NtFsControlFile( hVolume,
0,
0,
0,
&iosb,
FSCTL_QUERY_USN_JOURNAL,
0,
0,
&UsnJournalInfo,
sizeof UsnJournalInfo );
if ( NT_SUCCESS(Status) && NT_SUCCESS( iosb.Status ) )
{
// cool, it's a usn volume
}
else if ( ( STATUS_JOURNAL_NOT_ACTIVE == Status ||
STATUS_INVALID_DEVICE_STATE == Status ) )
{
//
// Usn journal not created, create it
//
CREATE_USN_JOURNAL_DATA usnCreateData;
usnCreateData.MaximumSize = 0x800000; // 8 meg
usnCreateData.AllocationDelta = 0x100000; // 1 meg
Status = NtFsControlFile( hVolume,
0,
0,
0,
&iosb,
FSCTL_CREATE_USN_JOURNAL,
&usnCreateData,
sizeof usnCreateData,
0,
0 );
if ( NT_SUCCESS( Status ) && NT_SUCCESS( iosb.Status ) )
{
Status = NtFsControlFile( hVolume,
0,
0,
0,
&iosb,
FSCTL_QUERY_USN_JOURNAL,
0,
0,
&UsnJournalInfo,
sizeof UsnJournalInfo );
if (! ( NT_SUCCESS(Status) && NT_SUCCESS( iosb.Status ) ) )
{
printf( "can't query usn vol info after creating it %#x\n", Status );
exit( 1 );
}
}
else
{
printf( "can't create usn journal %#x\n", Status );
exit( 1 );
}
}
else
{
printf( "can't get USN information, probably FAT: %#x\n", Status );
}
ullVolumeCreationTime = VolumeInfo.VolumeCreationTime.QuadPart;
ulVolumeSerialNumber = VolumeInfo.VolumeSerialNumber;
ullJournalId = UsnJournalInfo.UsnJournalID;
printf( " new volumecreationtime: %#I64x\n", ullVolumeCreationTime );
printf( " new volumeserialnumber: %#x\n", ulVolumeSerialNumber );
printf( " new journalid: %#I64x\n", ullJournalId );
CloseHandle( hVolume );
} //GetNtVolumeInformation
void PatchScopeTable( WCHAR const * pwcDir )
{
// Find out how many scopes are in the scope table
ULONG cScopes = 0;
{
WCHAR awcControl[ MAX_PATH ];
wcscpy( awcControl, pwcDir );
wcscat( awcControl, L"cisp0000.000" );
CMMFile Control( awcControl, FALSE );
if ( 0 == Control.GetMapping() )
{
printf( "can't open file %ws\n", awcControl );
exit( 1 );
}
cScopes = ((ULONG *) Control.GetMapping())[4];
}
WCHAR awcOne[ MAX_PATH ];
wcscpy( awcOne, pwcDir );
wcscat( awcOne, L"cisp0000.001" );
// Loop through the scopes and patch
{
printf( " scope table: %ws has %d scopes\n", awcOne, cScopes );
CMMFile One( awcOne, TRUE );
if ( !One.Ok() )
{
printf( "can't map scope table\n" );
exit( 1 );
}
BYTE * pb = (BYTE *) One.GetMapping();
for ( ULONG i = 0; i < cScopes; i++ )
{
const LONGLONG eSigCiScopeTable = 0x5158515851585158i64;
LONGLONG signature;
memcpy( &signature, pb, sizeof signature );
pb += sizeof signature;
if ( 0 == signature )
break;
printf( " scope record: \n" );
if ( eSigCiScopeTable != signature )
{
printf( "invalid scope signature: %#I64x\n", signature );
exit( 1 );
}
VOLUMEID volumeId;
memcpy( &volumeId, pb, sizeof volumeId );
printf( " volumeId: %x\n", volumeId );
pb += sizeof volumeId;
ULONGLONG ullNewVolumeCreationTime;
ULONG ulNewVolumeSerialNumber;
ULONGLONG ullNewJournalId;
GetNtVolumeInformation( volumeId,
ullNewVolumeCreationTime,
ulNewVolumeSerialNumber,
ullNewJournalId );
ULONGLONG ullVolumeCreationTime;
memcpy( &ullVolumeCreationTime, pb, sizeof ullVolumeCreationTime );
printf( " creation time: %#I64x\n", ullVolumeCreationTime );
memcpy( pb,
&ullNewVolumeCreationTime,
sizeof ullNewVolumeCreationTime );
pb += sizeof ullVolumeCreationTime;
ULONG ulVolumeSerialNumber;
memcpy( &ulVolumeSerialNumber, pb, sizeof ulVolumeSerialNumber );
printf( " serial number: %x\n", ulVolumeSerialNumber );
memcpy( pb,
&ulNewVolumeSerialNumber,
sizeof ulNewVolumeSerialNumber );
pb += sizeof ulVolumeSerialNumber;
if ( CI_VOLID_USN_NOT_ENABLED == volumeId )
{
FILETIME ft;
memcpy( &ft, pb, sizeof ft );
printf( " filetime: %#I64x\n", ft );
pb += sizeof ft;
}
else
{
USN usn;
memcpy( &usn, pb, sizeof usn );
printf( " usn: %#I64x\n", usn );
USN usnNewMax = 0;
memcpy( pb,
&usnNewMax,
sizeof usnNewMax );
pb += sizeof usn;
ULONGLONG JournalId;
memcpy( &JournalId, pb, sizeof JournalId );
printf( " JournalId: %#I64x\n", JournalId );
memcpy( pb,
&ullNewJournalId,
sizeof ullNewJournalId );
pb += sizeof JournalId;
}
ULONG cwcPath;
memcpy( &cwcPath, pb, sizeof cwcPath );
pb += sizeof cwcPath;
WCHAR awcPath[ MAX_PATH ];
memcpy( awcPath, pb, cwcPath * sizeof WCHAR );
printf( " path: %ws\n", awcPath );
pb += ( cwcPath * sizeof WCHAR );
}
One.Flush();
}
WCHAR awcTwo[ MAX_PATH ];
wcscpy( awcTwo, pwcDir );
wcscat( awcTwo, L"cisp0000.002" );
BOOL f = CopyFile( awcOne, awcTwo, FALSE );
if ( !f )
{
printf( "can't copy scope list, error %d\n", GetLastError() );
exit( 1 );
}
} //PatchScopeDir
extern "C" int __cdecl wmain( int argc, WCHAR * argv[] )
{
if ( 2 != argc )
Usage();
//
// The lone argument is the catalog directory, with or without catalog.wci
//
WCHAR awcDir[ MAX_PATH ];
wcscpy( awcDir, argv[1] );
AppendBackslash( awcDir );
if ( 0 == wcsistr( awcDir, L"catalog.wci" ) )
wcscat( awcDir, L"catalog.wci\\" );
// Find and validate the primary and secondary stores exist
WCHAR awcPri[MAX_PATH];
wcscpy( awcPri, awcDir );
wcscat( awcPri, L"*.ps1" );
WIN32_FIND_DATA findData;
HANDLE h = FindFirstFile( awcPri, &findData );
if ( INVALID_HANDLE_VALUE == h )
{
printf( "can't find primary *.ps1 store\n" );
exit( 1 );
}
FindClose( h );
wcscpy( awcPri, awcDir );
wcscat( awcPri, findData.cFileName );
WCHAR awcSec[MAX_PATH];
wcscpy( awcSec, awcDir );
wcscat( awcSec, L"*.ps2" );
h = FindFirstFile( awcSec, &findData );
if ( INVALID_HANDLE_VALUE == h )
{
printf( "can't find secondary *.ps2 store\n" );
exit( 1 );
}
FindClose( h );
wcscpy( awcSec, awcDir );
wcscat( awcSec, findData.cFileName );
//
// Do the core work here -- patch the file IDs in the primary store.
// Also whack the last seen times so the files aren't refiltered in
// case they were copied to the target machine after the catalog was
// snapped.
//
printf( "patching file IDs\n" );
PatchFileIDs( awcDir, awcPri, awcSec );
//
// Patch the scope table so cisvc doesn't think it's a different volume.
//
printf( "patching the scope table\n" );
PatchScopeTable( awcDir );
//
// Delete the old fileid hash table since the fileids are wrong.
// It'll get recreated automatically when the catalog is opened.
//
printf( "deleting the fileid hash map\n" );
WCHAR awcHashMap[ MAX_PATH ];
wcscpy( awcHashMap, awcDir );
wcscat( awcHashMap, L"cicat.fid" );
DeleteFile( awcHashMap );
printf( "catalog successfully converted\n" );
return 0;
} //wmain