4744 lines
121 KiB
C
4744 lines
121 KiB
C
/*++
|
||
|
||
Copyright (c) 1987-1997 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
chutil.c
|
||
|
||
Abstract:
|
||
|
||
Change Log utility routines.
|
||
|
||
Author:
|
||
|
||
|
||
Environment:
|
||
|
||
User mode only.
|
||
Contains NT-specific code.
|
||
Requires ANSI C extensions: slash-slash comments, long external names.
|
||
|
||
Revision History:
|
||
|
||
11-Jan-1994 (cliffv)
|
||
Split out from changelg.c
|
||
|
||
--*/
|
||
|
||
//
|
||
// Common include files.
|
||
//
|
||
|
||
#include "logonsrv.h" // Include files common to entire service
|
||
#pragma hdrstop
|
||
|
||
//
|
||
// Include files specific to this .c file
|
||
//
|
||
|
||
|
||
|
||
//
|
||
// Tables of related delta types
|
||
//
|
||
|
||
//
|
||
// Table of delete delta types.
|
||
// Index into the table with a delta type,
|
||
// the entry is the delta type that is used to delete the object.
|
||
//
|
||
// There are some objects that can't be deleted. In that case, this table
|
||
// contains a delta type that uniquely identifies the object. That allows
|
||
// this table to be used to see if two deltas describe the same object type.
|
||
//
|
||
|
||
const NETLOGON_DELTA_TYPE NlGlobalDeleteDeltaType[MAX_DELETE_DELTA+1]
|
||
= {
|
||
AddOrChangeDomain, // 0 is an invalid delta type
|
||
AddOrChangeDomain, // AddOrChangeDomain,
|
||
DeleteGroup, // AddOrChangeGroup,
|
||
DeleteGroup, // DeleteGroup,
|
||
DeleteGroup, // RenameGroup,
|
||
DeleteUser, // AddOrChangeUser,
|
||
DeleteUser, // DeleteUser,
|
||
DeleteUser, // RenameUser,
|
||
DeleteGroup, // ChangeGroupMembership,
|
||
DeleteAlias, // AddOrChangeAlias,
|
||
DeleteAlias, // DeleteAlias,
|
||
DeleteAlias, // RenameAlias,
|
||
DeleteAlias, // ChangeAliasMembership,
|
||
AddOrChangeLsaPolicy, // AddOrChangeLsaPolicy,
|
||
DeleteLsaTDomain, // AddOrChangeLsaTDomain,
|
||
DeleteLsaTDomain, // DeleteLsaTDomain,
|
||
DeleteLsaAccount, // AddOrChangeLsaAccount,
|
||
DeleteLsaAccount, // DeleteLsaAccount,
|
||
DeleteLsaSecret, // AddOrChangeLsaSecret,
|
||
DeleteLsaSecret, // DeleteLsaSecret,
|
||
DeleteGroup, // DeleteGroupByName,
|
||
DeleteUser, // DeleteUserByName,
|
||
SerialNumberSkip, // SerialNumberSkip,
|
||
DummyChangeLogEntry // DummyChangeLogEntry
|
||
};
|
||
|
||
|
||
//
|
||
// Table of add delta types.
|
||
// Index into the table with a delta type,
|
||
// the entry is the delta type that is used to add the object.
|
||
//
|
||
// There are some objects that can't be added. In that case, this table
|
||
// contains a delta type that uniquely identifies the object. That allows
|
||
// this table to be used to see if two deltas describe the same object type.
|
||
//
|
||
// In the table, Groups and Aliases are represented as renames. This causes
|
||
// NlPackSingleDelta to return both the group attributes and the group
|
||
// membership.
|
||
//
|
||
|
||
const NETLOGON_DELTA_TYPE NlGlobalAddDeltaType[MAX_ADD_DELTA+1]
|
||
= {
|
||
AddOrChangeDomain, // 0 is an invalid delta type
|
||
AddOrChangeDomain, // AddOrChangeDomain,
|
||
RenameGroup, // AddOrChangeGroup,
|
||
RenameGroup, // DeleteGroup,
|
||
RenameGroup, // RenameGroup,
|
||
AddOrChangeUser, // AddOrChangeUser,
|
||
AddOrChangeUser, // DeleteUser,
|
||
AddOrChangeUser, // RenameUser,
|
||
RenameGroup, // ChangeGroupMembership,
|
||
RenameAlias, // AddOrChangeAlias,
|
||
RenameAlias, // DeleteAlias,
|
||
RenameAlias, // RenameAlias,
|
||
RenameAlias, // ChangeAliasMembership,
|
||
AddOrChangeLsaPolicy, // AddOrChangeLsaPolicy,
|
||
AddOrChangeLsaTDomain, // AddOrChangeLsaTDomain,
|
||
AddOrChangeLsaTDomain, // DeleteLsaTDomain,
|
||
AddOrChangeLsaAccount, // AddOrChangeLsaAccount,
|
||
AddOrChangeLsaAccount, // DeleteLsaAccount,
|
||
AddOrChangeLsaSecret, // AddOrChangeLsaSecret,
|
||
AddOrChangeLsaSecret, // DeleteLsaSecret,
|
||
RenameGroup, // DeleteGroupByName,
|
||
AddOrChangeUser, // DeleteUserByName,
|
||
SerialNumberSkip, // SerialNumberSkip,
|
||
DummyChangeLogEntry // DummyChangeLogEntry
|
||
};
|
||
|
||
|
||
|
||
//
|
||
// Table of Status Codes indicating the object doesn't exist.
|
||
// Index into the table with a delta type.
|
||
//
|
||
// Map to STATUS_SUCCESS for the invalid cases to explicitly avoid other error
|
||
// codes.
|
||
|
||
const NTSTATUS NlGlobalObjectNotFoundStatus[MAX_OBJECT_NOT_FOUND_STATUS+1]
|
||
= {
|
||
STATUS_SUCCESS, // 0 is an invalid delta type
|
||
STATUS_NO_SUCH_DOMAIN, // AddOrChangeDomain,
|
||
STATUS_NO_SUCH_GROUP, // AddOrChangeGroup,
|
||
STATUS_NO_SUCH_GROUP, // DeleteGroup,
|
||
STATUS_NO_SUCH_GROUP, // RenameGroup,
|
||
STATUS_NO_SUCH_USER, // AddOrChangeUser,
|
||
STATUS_NO_SUCH_USER, // DeleteUser,
|
||
STATUS_NO_SUCH_USER, // RenameUser,
|
||
STATUS_NO_SUCH_GROUP, // ChangeGroupMembership,
|
||
STATUS_NO_SUCH_ALIAS, // AddOrChangeAlias,
|
||
STATUS_NO_SUCH_ALIAS, // DeleteAlias,
|
||
STATUS_NO_SUCH_ALIAS, // RenameAlias,
|
||
STATUS_NO_SUCH_ALIAS, // ChangeAliasMembership,
|
||
STATUS_SUCCESS, // AddOrChangeLsaPolicy,
|
||
STATUS_OBJECT_NAME_NOT_FOUND, // AddOrChangeLsaTDomain,
|
||
STATUS_OBJECT_NAME_NOT_FOUND, // DeleteLsaTDomain,
|
||
STATUS_OBJECT_NAME_NOT_FOUND, // AddOrChangeLsaAccount,
|
||
STATUS_OBJECT_NAME_NOT_FOUND, // DeleteLsaAccount,
|
||
STATUS_OBJECT_NAME_NOT_FOUND, // AddOrChangeLsaSecret,
|
||
STATUS_OBJECT_NAME_NOT_FOUND, // DeleteLsaSecret,
|
||
STATUS_NO_SUCH_GROUP, // DeleteGroupByName,
|
||
STATUS_NO_SUCH_USER, // DeleteUserByName,
|
||
STATUS_SUCCESS, // SerialNumberSkip,
|
||
STATUS_SUCCESS // DummyChangeLogEntry
|
||
};
|
||
|
||
|
||
|
||
//
|
||
// Context for I_NetLogonReadChangeLog
|
||
//
|
||
|
||
typedef struct _CHANGELOG_CONTEXT {
|
||
LARGE_INTEGER SerialNumber;
|
||
DWORD DbIndex;
|
||
DWORD SequenceNumber;
|
||
} CHANGELOG_CONTEXT, *PCHANGELOG_CONTEXT;
|
||
|
||
//
|
||
// Header for buffers returned from I_NetLogonReadChangeLog
|
||
//
|
||
|
||
typedef struct _CHANGELOG_BUFFER_HEADER {
|
||
DWORD Size;
|
||
DWORD Version;
|
||
DWORD SequenceNumber;
|
||
DWORD Flags;
|
||
} CHANGELOG_BUFFER_HEADER, *PCHANGELOG_BUFFER_HEADER;
|
||
|
||
#define CHANGELOG_BUFFER_VERSION 1
|
||
|
||
|
||
ULONG NlGlobalChangeLogHandle = 0;
|
||
ULONG NlGlobalChangeLogSequenceNumber;
|
||
|
||
/* NlCreateChangeLogFile and NlWriteChangeLogBytes reference each other */
|
||
NTSTATUS
|
||
NlWriteChangeLogBytes(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN LPBYTE Buffer,
|
||
IN DWORD BufferSize,
|
||
IN BOOLEAN FlushIt
|
||
);
|
||
|
||
|
||
|
||
|
||
NTSTATUS
|
||
NlCreateChangeLogFile(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Try to create a change log file. If it is successful then it sets
|
||
the file handle in ChangeLogDesc, otherwise it leaves the handle invalid.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer being used
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS - The Service completed successfully.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
WCHAR ChangeLogFile[PATHLEN+1];
|
||
|
||
NlAssert( ChangeLogDesc->FileHandle == INVALID_HANDLE_VALUE );
|
||
|
||
//
|
||
// if the change file name is unknown, terminate the operation.
|
||
//
|
||
|
||
if( NlGlobalChangeLogFilePrefix[0] == L'\0' ) {
|
||
return STATUS_NO_SUCH_FILE;
|
||
}
|
||
|
||
//
|
||
// Create change log file. If it exists already then truncate it.
|
||
//
|
||
// Note : if a valid change log file exists on the system, then we
|
||
// would have opened at initialization time.
|
||
//
|
||
|
||
wcscpy( ChangeLogFile, NlGlobalChangeLogFilePrefix );
|
||
wcscat( ChangeLogFile,
|
||
ChangeLogDesc->TempLog ? TEMP_CHANGELOG_FILE_POSTFIX : CHANGELOG_FILE_POSTFIX );
|
||
|
||
ChangeLogDesc->FileHandle = CreateFileW(
|
||
ChangeLogFile,
|
||
GENERIC_READ | GENERIC_WRITE,
|
||
FILE_SHARE_READ, // allow backups and debugging
|
||
NULL, // Supply better security ??
|
||
CREATE_ALWAYS, // Overwrites always
|
||
FILE_ATTRIBUTE_NORMAL,
|
||
NULL ); // No template
|
||
|
||
if (ChangeLogDesc->FileHandle == INVALID_HANDLE_VALUE) {
|
||
|
||
Status = NetpApiStatusToNtStatus( GetLastError());
|
||
NlPrint((NL_CRITICAL,"Unable to create changelog file: 0x%lx \n", Status));
|
||
return Status;
|
||
}
|
||
|
||
//
|
||
// Write cache in backup changelog file if the cache is valid.
|
||
//
|
||
|
||
if( ChangeLogDesc->Buffer != NULL ) {
|
||
Status = NlWriteChangeLogBytes(
|
||
ChangeLogDesc,
|
||
ChangeLogDesc->Buffer,
|
||
ChangeLogDesc->BufferSize,
|
||
TRUE ); // Flush the bytes to disk
|
||
|
||
return Status;
|
||
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlFlushChangeLog(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Flush any dirty buffers to the change log file itself.
|
||
Ensure they are flushed to disk.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer being used
|
||
|
||
Return Value:
|
||
|
||
Status of the operation.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS Status = STATUS_SUCCESS;
|
||
OVERLAPPED Overlapped;
|
||
DWORD BytesWritten;
|
||
DWORD BufferSize;
|
||
|
||
//
|
||
// If there's nothing to do,
|
||
// just return.
|
||
//
|
||
|
||
if ( ChangeLogDesc->LastDirtyByte == 0 ) {
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
//
|
||
// Write to the file.
|
||
//
|
||
|
||
if ( ChangeLogDesc->FileHandle == INVALID_HANDLE_VALUE ) {
|
||
|
||
Status = NlCreateChangeLogFile( ChangeLogDesc );
|
||
|
||
//
|
||
// This must have written entire buffer if it is successful
|
||
// creating the change log file.
|
||
//
|
||
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// if we are unable to create this into the changelog file, work
|
||
// with internal cache, but notify admin by sending admin alert.
|
||
//
|
||
|
||
if ( ChangeLogDesc->FileHandle != INVALID_HANDLE_VALUE ) {
|
||
|
||
#ifdef notdef
|
||
NlPrint((NL_CHANGELOG, "NlFlushChangeLog: %ld to %ld\n",
|
||
ChangeLogDesc->FirstDirtyByte,
|
||
ChangeLogDesc->LastDirtyByte ));
|
||
#endif // notdef
|
||
|
||
//
|
||
// Seek to appropriate offset in the file.
|
||
//
|
||
|
||
RtlZeroMemory( &Overlapped, sizeof(Overlapped) );
|
||
Overlapped.Offset = ChangeLogDesc->FirstDirtyByte;
|
||
|
||
//
|
||
// Actually write to the file.
|
||
//
|
||
|
||
BufferSize = ChangeLogDesc->LastDirtyByte -
|
||
ChangeLogDesc->FirstDirtyByte + 1;
|
||
|
||
if ( !WriteFile( ChangeLogDesc->FileHandle,
|
||
&ChangeLogDesc->Buffer[ChangeLogDesc->FirstDirtyByte],
|
||
BufferSize,
|
||
&BytesWritten,
|
||
&Overlapped ) ) {
|
||
|
||
Status = NetpApiStatusToNtStatus( GetLastError() );
|
||
NlPrint((NL_CRITICAL, "Write to ChangeLog failed 0x%lx\n",
|
||
Status ));
|
||
|
||
//
|
||
// Recreate changelog file
|
||
//
|
||
|
||
CloseHandle( ChangeLogDesc->FileHandle );
|
||
ChangeLogDesc->FileHandle = INVALID_HANDLE_VALUE;
|
||
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Ensure all the bytes made it.
|
||
//
|
||
|
||
if ( BytesWritten != BufferSize ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"Write to ChangeLog bad byte count %ld s.b. %ld\n",
|
||
BytesWritten,
|
||
BufferSize ));
|
||
|
||
//
|
||
// Recreate changelog file
|
||
//
|
||
|
||
CloseHandle( ChangeLogDesc->FileHandle );
|
||
ChangeLogDesc->FileHandle = INVALID_HANDLE_VALUE;
|
||
|
||
Status = STATUS_BUFFER_TOO_SMALL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Force the modifications to disk.
|
||
//
|
||
|
||
if ( !FlushFileBuffers( ChangeLogDesc->FileHandle ) ) {
|
||
|
||
Status = NetpApiStatusToNtStatus( GetLastError() );
|
||
NlPrint((NL_CRITICAL, "Flush to ChangeLog failed 0x%lx\n", Status ));
|
||
|
||
//
|
||
// Recreate changelog file
|
||
//
|
||
|
||
CloseHandle( ChangeLogDesc->FileHandle );
|
||
ChangeLogDesc->FileHandle = INVALID_HANDLE_VALUE;
|
||
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Indicate these byte successfully made it out to disk.
|
||
//
|
||
|
||
ChangeLogDesc->FirstDirtyByte = 0;
|
||
ChangeLogDesc->LastDirtyByte = 0;
|
||
}
|
||
|
||
Cleanup:
|
||
|
||
if( !NT_SUCCESS(Status) ) {
|
||
|
||
//
|
||
// Write event log.
|
||
//
|
||
|
||
NlpWriteEventlog (
|
||
NELOG_NetlogonChangeLogCorrupt,
|
||
EVENTLOG_ERROR_TYPE,
|
||
(LPBYTE)&Status,
|
||
sizeof(Status),
|
||
NULL,
|
||
0 );
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
NTSTATUS
|
||
NlWriteChangeLogBytes(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN LPBYTE Buffer,
|
||
IN DWORD BufferSize,
|
||
IN BOOLEAN FlushIt
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Write bytes from the changelog cache to the change log file.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer being used
|
||
|
||
Buffer - Address within the changelog cache to write.
|
||
|
||
BufferSize - Number of bytes to write.
|
||
|
||
FlushIt - TRUE if the bytes are to be flushed to disk
|
||
|
||
Return Value:
|
||
|
||
Status of the operation.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS Status;
|
||
ULONG FirstDirtyByte;
|
||
ULONG LastDirtyByte;
|
||
|
||
//
|
||
// Compute the new range of dirty bytes.
|
||
//
|
||
|
||
FirstDirtyByte = (ULONG)(((LPBYTE)Buffer) - ((LPBYTE)ChangeLogDesc->Buffer));
|
||
LastDirtyByte = FirstDirtyByte + BufferSize - 1;
|
||
|
||
#ifdef notdef
|
||
NlPrint((NL_CHANGELOG, "NlWriteChangeLogBytes: %ld to %ld\n",
|
||
FirstDirtyByte,
|
||
LastDirtyByte ));
|
||
#endif // notdef
|
||
|
||
if ( ChangeLogDesc->LastDirtyByte == 0 ) {
|
||
ChangeLogDesc->FirstDirtyByte = FirstDirtyByte;
|
||
ChangeLogDesc->LastDirtyByte = LastDirtyByte;
|
||
} else {
|
||
if ( ChangeLogDesc->FirstDirtyByte > FirstDirtyByte ) {
|
||
ChangeLogDesc->FirstDirtyByte = FirstDirtyByte;
|
||
}
|
||
if ( ChangeLogDesc->LastDirtyByte < LastDirtyByte ) {
|
||
ChangeLogDesc->LastDirtyByte = LastDirtyByte;
|
||
}
|
||
}
|
||
|
||
//
|
||
// If the bytes are to be flushed,
|
||
// do so.
|
||
//
|
||
|
||
if ( FlushIt ) {
|
||
Status = NlFlushChangeLog( ChangeLogDesc );
|
||
return Status;
|
||
}
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
|
||
|
||
PCHANGELOG_BLOCK_HEADER
|
||
NlMoveToNextChangeLogBlock(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN PCHANGELOG_BLOCK_HEADER BlockPtr
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function accepts a pointer to a change log
|
||
block and returns the pointer to the next change log block in the
|
||
buffer. It however wraps around the change log cache.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer being used
|
||
|
||
BlockPtr - pointer to a change log block.
|
||
|
||
Return Value:
|
||
|
||
Returns the pointer to the next change log block in the list.
|
||
|
||
--*/
|
||
{
|
||
PCHANGELOG_BLOCK_HEADER ReturnPtr;
|
||
|
||
ReturnPtr = (PCHANGELOG_BLOCK_HEADER)
|
||
((LPBYTE)BlockPtr + BlockPtr->BlockSize);
|
||
|
||
|
||
NlAssert( (LPBYTE)ReturnPtr <= ChangeLogDesc->BufferEnd );
|
||
|
||
if( (LPBYTE)ReturnPtr >= ChangeLogDesc->BufferEnd ) {
|
||
|
||
//
|
||
// wrap around
|
||
//
|
||
|
||
ReturnPtr = ChangeLogDesc->FirstBlock;
|
||
}
|
||
|
||
return ReturnPtr;
|
||
|
||
}
|
||
|
||
|
||
PCHANGELOG_BLOCK_HEADER
|
||
NlMoveToPrevChangeLogBlock(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN PCHANGELOG_BLOCK_HEADER BlockPtr
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function accepts a pointer to a change log
|
||
block and returns the pointer to the next change log block in the
|
||
buffer. It however wraps around the change log cache.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer being used
|
||
|
||
BlockPtr - pointer to a change log block.
|
||
|
||
Return Value:
|
||
|
||
Returns the pointer to the next change log block in the list.
|
||
|
||
--*/
|
||
{
|
||
PCHANGELOG_BLOCK_HEADER ReturnPtr;
|
||
PCHANGELOG_BLOCK_TRAILER ReturnTrailer;
|
||
|
||
//
|
||
// If this is the first block in the buffer,
|
||
// return the last block in the buffer.
|
||
//
|
||
|
||
if ( BlockPtr == ChangeLogDesc->FirstBlock ) {
|
||
ReturnTrailer = (PCHANGELOG_BLOCK_TRAILER)
|
||
(ChangeLogDesc->BufferEnd - sizeof(CHANGELOG_BLOCK_TRAILER));
|
||
|
||
//
|
||
// Otherwise return the buffer immediately before this one.
|
||
//
|
||
|
||
} else {
|
||
ReturnTrailer = (PCHANGELOG_BLOCK_TRAILER)
|
||
(((LPBYTE)BlockPtr) - sizeof(CHANGELOG_BLOCK_TRAILER));
|
||
}
|
||
|
||
|
||
ReturnPtr = (PCHANGELOG_BLOCK_HEADER)
|
||
((LPBYTE)ReturnTrailer -
|
||
ReturnTrailer->BlockSize +
|
||
sizeof(CHANGELOG_BLOCK_TRAILER) );
|
||
|
||
|
||
NlAssert( ReturnPtr >= ChangeLogDesc->FirstBlock );
|
||
NlAssert( (LPBYTE)ReturnPtr < ChangeLogDesc->BufferEnd );
|
||
|
||
return ReturnPtr;
|
||
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
NlAllocChangeLogBlock(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN DWORD BlockSize,
|
||
OUT PCHANGELOG_BLOCK_HEADER *AllocatedBlock
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function will allocate a change log block from the free block
|
||
at the tail of the change log circular list. If the available free
|
||
block size is less than the required size than it will enlarge the
|
||
free block by the freeing up change logs from the header. Once the
|
||
free block is larger then it will cut the block to the required size
|
||
and adjust the free block pointer.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer being used
|
||
|
||
BlockSize - size of the change log block required.
|
||
|
||
AllocatedBlock - Returns the pointer to the block that is allocated.
|
||
|
||
Return Value:
|
||
|
||
Status of the operation
|
||
|
||
--*/
|
||
{
|
||
PCHANGELOG_BLOCK_HEADER FreeBlock;
|
||
PCHANGELOG_BLOCK_HEADER NewBlock;
|
||
DWORD ReqBlockSize;
|
||
DWORD AllocatedBlockSize;
|
||
|
||
//
|
||
// pump up the size to include block header, block trailer,
|
||
// and to align to DWORD.
|
||
//
|
||
// Add in the size of the new free block immediately following the new
|
||
// block.
|
||
//
|
||
|
||
AllocatedBlockSize =
|
||
ROUND_UP_COUNT( sizeof(CHANGELOG_BLOCK_HEADER), ALIGN_WORST) +
|
||
ROUND_UP_COUNT( BlockSize+sizeof(CHANGELOG_BLOCK_TRAILER), ALIGN_WORST);
|
||
|
||
ReqBlockSize = AllocatedBlockSize +
|
||
ROUND_UP_COUNT( sizeof(CHANGELOG_BLOCK_HEADER), ALIGN_WORST) +
|
||
ROUND_UP_COUNT( sizeof(CHANGELOG_BLOCK_TRAILER), ALIGN_WORST );
|
||
|
||
if ( ReqBlockSize >= ChangeLogDesc->BufferSize - 16 ) {
|
||
return STATUS_ALLOTTED_SPACE_EXCEEDED;
|
||
}
|
||
|
||
|
||
//
|
||
// If the current free block isn't big enough,
|
||
// make it big enough.
|
||
//
|
||
|
||
FreeBlock = ChangeLogDesc->Tail;
|
||
|
||
NlAssert( FreeBlock->BlockState == BlockFree );
|
||
|
||
while ( FreeBlock->BlockSize <= ReqBlockSize ) {
|
||
|
||
//
|
||
// If this is a change log,
|
||
// make the free block bigger by wrapping around.
|
||
//
|
||
|
||
{
|
||
PCHANGELOG_BLOCK_HEADER NextFreeBlock;
|
||
|
||
NextFreeBlock = NlMoveToNextChangeLogBlock( ChangeLogDesc, FreeBlock );
|
||
|
||
|
||
//
|
||
// If this free block is the end block in the cache,
|
||
// so make this as a 'hole' block and wrap around for
|
||
// next free block.
|
||
//
|
||
|
||
if( (LPBYTE)NextFreeBlock !=
|
||
(LPBYTE)FreeBlock + FreeBlock->BlockSize ) {
|
||
|
||
NlAssert( ((LPBYTE)FreeBlock + FreeBlock->BlockSize) ==
|
||
ChangeLogDesc->BufferEnd );
|
||
|
||
NlAssert( NextFreeBlock == ChangeLogDesc->FirstBlock );
|
||
|
||
FreeBlock->BlockState = BlockHole;
|
||
|
||
//
|
||
// Write the 'hole' block status in the file.
|
||
// (Write the entire block since the block size in the trailer
|
||
// may have changed on previous iterations of this loop.)
|
||
//
|
||
|
||
(VOID) NlWriteChangeLogBytes( ChangeLogDesc,
|
||
(LPBYTE) FreeBlock,
|
||
FreeBlock->BlockSize,
|
||
TRUE ); // Flush the bytes to disk
|
||
|
||
//
|
||
// The free block is now at the front of the cache.
|
||
//
|
||
|
||
FreeBlock = ChangeLogDesc->FirstBlock;
|
||
FreeBlock->BlockState = BlockFree;
|
||
|
||
//
|
||
// Otherwise, enlarge the current free block by merging the next
|
||
// block into it. The next free block is either a used block or
|
||
// the 'hole' block.
|
||
//
|
||
} else {
|
||
|
||
//
|
||
// If we've just deleted a used block,
|
||
// adjust the entry count.
|
||
//
|
||
// VOID_DB entries are "deleted" entries and have already adjusted
|
||
// the entry count.
|
||
//
|
||
if ( NextFreeBlock->BlockState == BlockUsed ) {
|
||
DWORD DBIndex = ((PCHANGELOG_ENTRY)(NextFreeBlock+1))->DBIndex;
|
||
if ( DBIndex != VOID_DB ) {
|
||
ChangeLogDesc->EntryCount[DBIndex] --;
|
||
}
|
||
}
|
||
|
||
FreeBlock->BlockSize += NextFreeBlock->BlockSize;
|
||
ChangeLogBlockTrailer(FreeBlock)->BlockSize = FreeBlock->BlockSize;
|
||
}
|
||
|
||
|
||
//
|
||
// If we've consumed the head of the cache,
|
||
// move the head of the cache to the next block.
|
||
//
|
||
|
||
if ( NextFreeBlock == ChangeLogDesc->Head ) {
|
||
|
||
ChangeLogDesc->Head = NlMoveToNextChangeLogBlock( ChangeLogDesc,
|
||
NextFreeBlock );
|
||
|
||
//
|
||
// if we have moved the global header to hole block,
|
||
// skip and merge it to free block
|
||
//
|
||
|
||
NextFreeBlock = ChangeLogDesc->Head;
|
||
|
||
if (NextFreeBlock->BlockState == BlockHole ) {
|
||
|
||
FreeBlock->BlockSize += NextFreeBlock->BlockSize;
|
||
ChangeLogBlockTrailer(FreeBlock)->BlockSize = FreeBlock->BlockSize;
|
||
|
||
ChangeLogDesc->Head =
|
||
NlMoveToNextChangeLogBlock( ChangeLogDesc, NextFreeBlock );
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// NlAssert(ChangeLogDesc->Head->BlockState == BlockUsed );
|
||
//
|
||
// This assertion is overactive in case the whole buffer becomes free
|
||
// as it does in the following scenario. Suppose after allocating the
|
||
// whole buffer, we allocate a block that is larger than the half of the
|
||
// buffer. Then the head (marked used) points to the beginning of the
|
||
// buffer and the tail points to the free part at the end of the buffer.
|
||
// Then we allocate another block that is of the same size as the
|
||
// previously allocated block. In this case the free block at the end of
|
||
// the buffer will be marked 'hole', the head will be consumed, the head
|
||
// will be moved to the next (hole) block, the head will be moved further
|
||
// (since it points to a hole block) and marked free. So we end up with the
|
||
// buffer that is completely free; the head points to the beginning of the
|
||
// buffer and marked free, not used.
|
||
|
||
}
|
||
|
||
NlAssert( (FreeBlock >= ChangeLogDesc->FirstBlock) &&
|
||
(FreeBlock->BlockSize <= ChangeLogDesc->BufferSize) &&
|
||
( ((LPBYTE)FreeBlock + FreeBlock->BlockSize) <=
|
||
ChangeLogDesc->BufferEnd) );
|
||
|
||
//
|
||
// Cut the free block ...
|
||
//
|
||
|
||
NewBlock = FreeBlock;
|
||
|
||
FreeBlock = (PCHANGELOG_BLOCK_HEADER)
|
||
((LPBYTE)FreeBlock + AllocatedBlockSize);
|
||
|
||
FreeBlock->BlockState = BlockFree;
|
||
FreeBlock->BlockSize = NewBlock->BlockSize - AllocatedBlockSize;
|
||
ChangeLogBlockTrailer(FreeBlock)->BlockSize = FreeBlock->BlockSize;
|
||
|
||
ChangeLogDesc->Tail = FreeBlock;
|
||
|
||
RtlZeroMemory( NewBlock, AllocatedBlockSize );
|
||
NewBlock->BlockState = BlockUsed;
|
||
NewBlock->BlockSize = AllocatedBlockSize;
|
||
ChangeLogBlockTrailer(NewBlock)->BlockSize = NewBlock->BlockSize;
|
||
|
||
NlAssert( (NewBlock >= ChangeLogDesc->FirstBlock) &&
|
||
( ((LPBYTE)NewBlock + BlockSize) <= ChangeLogDesc->BufferEnd) );
|
||
|
||
*AllocatedBlock = NewBlock;
|
||
|
||
return STATUS_SUCCESS;
|
||
|
||
}
|
||
|
||
|
||
PCHANGELOG_ENTRY
|
||
NlMoveToNextChangeLogEntry(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN PCHANGELOG_ENTRY ChangeLogEntry
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is a worker routine to scan the change log list. This
|
||
accepts a pointer to a change log structure and returns a pointer to
|
||
the next change log structure. It returns NULL pointer if the given
|
||
struct is the last change log structure in the list.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer being used
|
||
|
||
ChangeLogEntry - pointer to a change log strcuture.
|
||
|
||
Return Value:
|
||
|
||
Returns the pointer to the next change log structure in the list.
|
||
|
||
--*/
|
||
{
|
||
PCHANGELOG_BLOCK_HEADER ChangeLogBlock;
|
||
|
||
ChangeLogBlock = (PCHANGELOG_BLOCK_HEADER)
|
||
( (LPBYTE) ChangeLogEntry - sizeof(CHANGELOG_BLOCK_HEADER) );
|
||
|
||
NlAssert( ChangeLogBlock->BlockState == BlockUsed );
|
||
|
||
ChangeLogBlock = NlMoveToNextChangeLogBlock( ChangeLogDesc, ChangeLogBlock );
|
||
|
||
//
|
||
// If we're at the end of the list,
|
||
// return null
|
||
//
|
||
if ( ChangeLogBlock->BlockState == BlockFree ) {
|
||
return NULL;
|
||
|
||
|
||
//
|
||
// Skip this block, there will be only one 'Hole' block in the
|
||
// list.
|
||
//
|
||
} else if ( ChangeLogBlock->BlockState == BlockHole ) {
|
||
|
||
|
||
ChangeLogBlock = NlMoveToNextChangeLogBlock( ChangeLogDesc, ChangeLogBlock );
|
||
|
||
if ( ChangeLogBlock->BlockState == BlockFree ) {
|
||
return NULL;
|
||
}
|
||
|
||
}
|
||
|
||
NlAssert( ChangeLogBlock->BlockState == BlockUsed );
|
||
|
||
return (PCHANGELOG_ENTRY)
|
||
( (LPBYTE)ChangeLogBlock + sizeof(CHANGELOG_BLOCK_HEADER) );
|
||
|
||
}
|
||
|
||
|
||
PCHANGELOG_ENTRY
|
||
NlMoveToPrevChangeLogEntry(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN PCHANGELOG_ENTRY ChangeLogEntry
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is a worker routine to scan the change log list. This
|
||
accepts a pointer to a change log structure and returns a pointer to
|
||
the previous change log structure. It returns NULL pointer if the given
|
||
struct is the first change log structure in the list.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer being used
|
||
|
||
ChangeLogEntry - pointer to a change log strcuture.
|
||
|
||
Return Value:
|
||
|
||
Returns the pointer to the next change log structure in the list.
|
||
|
||
--*/
|
||
{
|
||
PCHANGELOG_BLOCK_HEADER ChangeLogBlock;
|
||
|
||
ChangeLogBlock = (PCHANGELOG_BLOCK_HEADER)
|
||
( (LPBYTE) ChangeLogEntry - sizeof(CHANGELOG_BLOCK_HEADER) );
|
||
|
||
NlAssert( ChangeLogBlock->BlockState == BlockUsed ||
|
||
ChangeLogBlock->BlockState == BlockFree );
|
||
|
||
ChangeLogBlock = NlMoveToPrevChangeLogBlock( ChangeLogDesc, ChangeLogBlock );
|
||
|
||
//
|
||
// If we're at the end of the list,
|
||
// return null
|
||
//
|
||
if ( ChangeLogBlock->BlockState == BlockFree ) {
|
||
return NULL;
|
||
|
||
|
||
//
|
||
// Skip this block, there will be only one 'Hole' block in the
|
||
// list.
|
||
//
|
||
} else if ( ChangeLogBlock->BlockState == BlockHole ) {
|
||
|
||
|
||
ChangeLogBlock = NlMoveToPrevChangeLogBlock( ChangeLogDesc, ChangeLogBlock );
|
||
|
||
if ( ChangeLogBlock->BlockState == BlockFree ) {
|
||
return NULL;
|
||
}
|
||
|
||
}
|
||
|
||
NlAssert( ChangeLogBlock->BlockState == BlockUsed );
|
||
|
||
return (PCHANGELOG_ENTRY)
|
||
( (LPBYTE)ChangeLogBlock + sizeof(CHANGELOG_BLOCK_HEADER) );
|
||
|
||
}
|
||
|
||
|
||
PCHANGELOG_ENTRY
|
||
NlFindFirstChangeLogEntry(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN DWORD DBIndex
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Returns a pointer to the first change log entry for the specified
|
||
database.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer being used
|
||
|
||
DBIndex - Describes which database to find the changelog entry for.
|
||
|
||
Return Value:
|
||
|
||
Non-NULL - change log entry found
|
||
|
||
NULL - No such entry exists.
|
||
|
||
--*/
|
||
{
|
||
PCHANGELOG_ENTRY ChangeLogEntry;
|
||
|
||
//
|
||
// If nothing has ever been written to the change log,
|
||
// indicate nothing is available.
|
||
//
|
||
|
||
if ( ChangeLogIsEmpty( ChangeLogDesc ) ) {
|
||
return NULL;
|
||
}
|
||
|
||
for ( ChangeLogEntry = (PCHANGELOG_ENTRY) (ChangeLogDesc->Head + 1);
|
||
ChangeLogEntry != NULL ;
|
||
ChangeLogEntry = NlMoveToNextChangeLogEntry( ChangeLogDesc, ChangeLogEntry) ) {
|
||
|
||
if( ChangeLogEntry->DBIndex == (UCHAR) DBIndex ) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
return ChangeLogEntry;
|
||
}
|
||
|
||
|
||
|
||
PCHANGELOG_ENTRY
|
||
NlFindChangeLogEntry(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN LARGE_INTEGER SerialNumber,
|
||
IN BOOL DownLevel,
|
||
IN BOOL NeedExactMatch,
|
||
IN DWORD DBIndex
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Search the change log entry in change log cache for a given serial
|
||
number
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer being used
|
||
|
||
SerialNumber - Serial number of the entry to find.
|
||
|
||
DownLevel - True if only the least significant portion of the serial
|
||
number needs to match.
|
||
|
||
NeedExactMatch - True if the caller wants us to exactly match the
|
||
specified serial number.
|
||
|
||
DBIndex - Describes which database to find the changelog entry for.
|
||
|
||
Return Value:
|
||
|
||
Non-NULL - change log entry found
|
||
|
||
NULL - No such entry exists.
|
||
|
||
--*/
|
||
{
|
||
PCHANGELOG_ENTRY ChangeLogEntry;
|
||
PCHANGELOG_ENTRY PriorChangeLogEntry = NULL;
|
||
|
||
//
|
||
// If nothing has ever been written to the change log,
|
||
// indicate nothing is available.
|
||
//
|
||
|
||
if ( ChangeLogIsEmpty( ChangeLogDesc ) ) {
|
||
return NULL;
|
||
}
|
||
|
||
//
|
||
// Search from the tail of the changelog. For huge changelogs, this should
|
||
// reduce the working set size since we almost always search for one of
|
||
// the last few entries.
|
||
//
|
||
|
||
ChangeLogEntry = (PCHANGELOG_ENTRY) (ChangeLogDesc->Tail + 1);
|
||
|
||
|
||
while ( ( ChangeLogEntry =
|
||
NlMoveToPrevChangeLogEntry( ChangeLogDesc, ChangeLogEntry) ) != NULL ) {
|
||
|
||
if( ChangeLogEntry->DBIndex == (UCHAR) DBIndex ) {
|
||
|
||
if ( DownLevel ) {
|
||
if ( ChangeLogEntry->SerialNumber.LowPart ==
|
||
SerialNumber.LowPart ) {
|
||
return ChangeLogEntry;
|
||
}
|
||
} else {
|
||
if ( IsSerialNumberEqual( ChangeLogDesc, ChangeLogEntry, &SerialNumber) ){
|
||
if ( NeedExactMatch &&
|
||
ChangeLogEntry->SerialNumber.QuadPart != SerialNumber.QuadPart ) {
|
||
return NULL;
|
||
}
|
||
return ChangeLogEntry;
|
||
}
|
||
|
||
}
|
||
|
||
PriorChangeLogEntry = ChangeLogEntry;
|
||
|
||
}
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
|
||
PCHANGELOG_ENTRY
|
||
NlDuplicateChangeLogEntry(
|
||
IN PCHANGELOG_ENTRY ChangeLogEntry,
|
||
OUT LPDWORD ChangeLogEntrySize OPTIONAL
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Duplicate the specified changelog entry into an allocated buffer.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogEntry -- points to the changelog entry to duplicate
|
||
|
||
ChangeLogEntrySize - Optionally returns the size (in bytes) of the
|
||
returned change log entry.
|
||
|
||
Return Value:
|
||
|
||
NULL - Not enough memory to duplicate the change log entry
|
||
|
||
Non-NULL - returns a pointer to the duplicate change log entry. This buffer
|
||
must be freed via NetpMemoryFree.
|
||
|
||
--*/
|
||
{
|
||
PCHANGELOG_ENTRY TempChangeLogEntry;
|
||
ULONG Size;
|
||
PCHANGELOG_BLOCK_HEADER ChangeLogBlock;
|
||
|
||
ChangeLogBlock = (PCHANGELOG_BLOCK_HEADER)
|
||
( (LPBYTE) ChangeLogEntry - sizeof(CHANGELOG_BLOCK_HEADER) );
|
||
|
||
Size = ChangeLogBlock->BlockSize -
|
||
sizeof(CHANGELOG_BLOCK_HEADER) -
|
||
sizeof(CHANGELOG_BLOCK_TRAILER);
|
||
|
||
TempChangeLogEntry = (PCHANGELOG_ENTRY) NetpMemoryAllocate( Size );
|
||
|
||
if( TempChangeLogEntry == NULL ) {
|
||
return NULL;
|
||
}
|
||
|
||
RtlCopyMemory( TempChangeLogEntry, ChangeLogEntry, Size );
|
||
|
||
if ( ChangeLogEntrySize != NULL ) {
|
||
*ChangeLogEntrySize = Size;
|
||
}
|
||
|
||
return TempChangeLogEntry;
|
||
}
|
||
|
||
|
||
|
||
PCHANGELOG_ENTRY
|
||
NlFindPromotionChangeLogEntry(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN LARGE_INTEGER SerialNumber,
|
||
IN DWORD DBIndex
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Find the last change log entry with the same promotion count
|
||
as SerialNumber.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer being used
|
||
|
||
SerialNumber - Serial number containing the promotion count to query.
|
||
|
||
DBIndex - Describes which database to find the changelog entry for.
|
||
|
||
Return Value:
|
||
|
||
Non-NULL - returns a pointer to the duplicate change log entry. This buffer
|
||
must be freed via NetpMemoryFree.
|
||
|
||
NULL - No such entry exists.
|
||
|
||
--*/
|
||
{
|
||
PCHANGELOG_ENTRY ChangeLogEntry;
|
||
LONG GoalPromotionCount;
|
||
LONG PromotionCount;
|
||
|
||
//
|
||
// If nothing has ever been written to the change log,
|
||
// indicate nothing is available.
|
||
//
|
||
|
||
if ( ChangeLogIsEmpty( ChangeLogDesc ) ) {
|
||
return NULL;
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Search from the tail of the changelog. For huge changelogs, this should
|
||
// reduce the working set size since we almost always search for one of
|
||
// the last few entries.
|
||
//
|
||
|
||
ChangeLogEntry = (PCHANGELOG_ENTRY) (ChangeLogDesc->Tail + 1);
|
||
GoalPromotionCount = SerialNumber.HighPart & NlGlobalChangeLogPromotionMask;
|
||
|
||
while ( ( ChangeLogEntry =
|
||
NlMoveToPrevChangeLogEntry( ChangeLogDesc, ChangeLogEntry) ) != NULL ) {
|
||
|
||
if( ChangeLogEntry->DBIndex == (UCHAR) DBIndex ) {
|
||
PromotionCount = ChangeLogEntry->SerialNumber.HighPart & NlGlobalChangeLogPromotionMask;
|
||
|
||
//
|
||
// If the Current Change Log entry has a greater promotion count,
|
||
// continue searching backward.
|
||
//
|
||
|
||
if ( PromotionCount > GoalPromotionCount ) {
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// If the current change log entry has a smaller promotion count,
|
||
// indicate we couldn't find a change log entry.
|
||
//
|
||
|
||
if ( PromotionCount < GoalPromotionCount ) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Otherwise, success
|
||
//
|
||
|
||
return NlDuplicateChangeLogEntry( ChangeLogEntry, NULL );
|
||
|
||
}
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
|
||
PCHANGELOG_ENTRY
|
||
NlGetNextDownlevelChangeLogEntry(
|
||
ULONG DownlevelSerialNumber
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Find the change log entry for the delta with a serial number greater
|
||
than the one specified.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
DownlevelSerialNumber - The downlevel serial number
|
||
|
||
Return Value:
|
||
|
||
Non-NULL - change log entry found. This changelog entry must be
|
||
deallocated using NetpMemoryFree.
|
||
|
||
NULL - No such entry exists.
|
||
|
||
--*/
|
||
{
|
||
PCHANGELOG_ENTRY ChangeLogEntry;
|
||
LARGE_INTEGER SerialNumber;
|
||
|
||
SerialNumber.QuadPart = DownlevelSerialNumber + 1;
|
||
|
||
ChangeLogEntry = NlFindChangeLogEntry( &NlGlobalChangeLogDesc, SerialNumber, TRUE, TRUE, SAM_DB);
|
||
|
||
if ( ChangeLogEntry == NULL ||
|
||
ChangeLogEntry->DeltaType == DummyChangeLogEntry ) {
|
||
return NULL;
|
||
}
|
||
|
||
return NlDuplicateChangeLogEntry( ChangeLogEntry, NULL );
|
||
}
|
||
|
||
|
||
PCHANGELOG_ENTRY
|
||
NlFindNextChangeLogEntry(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN PCHANGELOG_ENTRY LastChangeLogEntry,
|
||
IN DWORD DBIndex
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Find the next change log entry in change log following a particular
|
||
changelog entry.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer being used
|
||
|
||
LastChangeLogEntry - last found changelog entry.
|
||
|
||
DBIndex - database index of the next entry to find
|
||
|
||
Return Value:
|
||
|
||
Non-null - change log entry found
|
||
|
||
NULL - No such entry exists.
|
||
|
||
|
||
--*/
|
||
{
|
||
PCHANGELOG_ENTRY NextChangeLogEntry = LastChangeLogEntry;
|
||
LARGE_INTEGER SerialNumber;
|
||
|
||
//
|
||
// Loop through the log finding this entry starting from the last
|
||
// found record.
|
||
//
|
||
|
||
SerialNumber.QuadPart = LastChangeLogEntry->SerialNumber.QuadPart + 1;
|
||
while ( ( NextChangeLogEntry =
|
||
NlMoveToNextChangeLogEntry( ChangeLogDesc, NextChangeLogEntry) ) != NULL ) {
|
||
|
||
if( NextChangeLogEntry->DBIndex == DBIndex ) {
|
||
|
||
//
|
||
// next log entry in the change log for
|
||
// this database. The serial number should match.
|
||
//
|
||
|
||
if ( !IsSerialNumberEqual( ChangeLogDesc, NextChangeLogEntry, &SerialNumber) ) {
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"NlFindNextChangeLogEntry: Serial numbers not contigous %lx %lx and %lx %lx\n",
|
||
NextChangeLogEntry->SerialNumber.HighPart,
|
||
NextChangeLogEntry->SerialNumber.LowPart,
|
||
SerialNumber.HighPart,
|
||
SerialNumber.LowPart ));
|
||
|
||
//
|
||
// write event log
|
||
//
|
||
|
||
NlpWriteEventlog (
|
||
NELOG_NetlogonChangeLogCorrupt,
|
||
EVENTLOG_ERROR_TYPE,
|
||
(LPBYTE)&DBIndex,
|
||
sizeof(DBIndex),
|
||
NULL,
|
||
0 );
|
||
|
||
return NULL;
|
||
|
||
}
|
||
|
||
return NextChangeLogEntry;
|
||
|
||
}
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
|
||
BOOLEAN
|
||
NlCompareChangeLogEntries(
|
||
IN PCHANGELOG_ENTRY ChangeLogEntry1,
|
||
IN PCHANGELOG_ENTRY ChangeLogEntry2
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
The two change log entries are compared to see if the are for the same
|
||
object. If
|
||
|
||
Arguments:
|
||
|
||
ChangeLogEntry1 - First change log entry to compare.
|
||
|
||
ChangeLogEntry2 - Second change log entry to compare.
|
||
|
||
Return Value:
|
||
|
||
TRUE - iff the change log entries are for the same object.
|
||
|
||
--*/
|
||
{
|
||
//
|
||
// Ensure the DbIndex is the same for both entries.
|
||
//
|
||
|
||
if ( ChangeLogEntry1->DBIndex != ChangeLogEntry2->DBIndex ) {
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Ensure the entries both describe the same object type.
|
||
//
|
||
|
||
if ( ChangeLogEntry1->DeltaType >= MAX_DELETE_DELTA ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"NlCompateChangeLogEntries: invalid delta type %lx\n",
|
||
ChangeLogEntry1->DeltaType ));
|
||
return FALSE;
|
||
}
|
||
|
||
if ( ChangeLogEntry2->DeltaType >= MAX_DELETE_DELTA ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"NlCompateChangeLogEntries: invalid delta type %lx\n",
|
||
ChangeLogEntry2->DeltaType ));
|
||
return FALSE;
|
||
}
|
||
|
||
if ( NlGlobalDeleteDeltaType[ChangeLogEntry1->DeltaType] !=
|
||
NlGlobalDeleteDeltaType[ChangeLogEntry2->DeltaType] ) {
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Depending on the delta type, ensure the entries refer to the same object.
|
||
//
|
||
|
||
switch(ChangeLogEntry1->DeltaType) {
|
||
|
||
case AddOrChangeGroup:
|
||
case DeleteGroup:
|
||
case RenameGroup:
|
||
case AddOrChangeUser:
|
||
case DeleteUser:
|
||
case RenameUser:
|
||
case ChangeGroupMembership:
|
||
case AddOrChangeAlias:
|
||
case DeleteAlias:
|
||
case RenameAlias:
|
||
case ChangeAliasMembership:
|
||
|
||
if (ChangeLogEntry1->ObjectRid == ChangeLogEntry2->ObjectRid ) {
|
||
return TRUE;
|
||
}
|
||
break;
|
||
|
||
|
||
case AddOrChangeLsaTDomain:
|
||
case DeleteLsaTDomain:
|
||
case AddOrChangeLsaAccount:
|
||
case DeleteLsaAccount:
|
||
|
||
NlAssert( ChangeLogEntry1->Flags & CHANGELOG_SID_SPECIFIED );
|
||
NlAssert( ChangeLogEntry2->Flags & CHANGELOG_SID_SPECIFIED );
|
||
|
||
if( (ChangeLogEntry1->Flags & CHANGELOG_SID_SPECIFIED) == 0 ||
|
||
(ChangeLogEntry2->Flags & CHANGELOG_SID_SPECIFIED) == 0) {
|
||
break;
|
||
}
|
||
|
||
if( RtlEqualSid(
|
||
(PSID)((LPBYTE)ChangeLogEntry1 + sizeof(CHANGELOG_ENTRY)),
|
||
(PSID)((LPBYTE)ChangeLogEntry2 + sizeof(CHANGELOG_ENTRY))) ) {
|
||
|
||
return TRUE;
|
||
}
|
||
break;
|
||
|
||
case AddOrChangeLsaSecret:
|
||
case DeleteLsaSecret:
|
||
|
||
NlAssert( ChangeLogEntry1->Flags & CHANGELOG_NAME_SPECIFIED );
|
||
NlAssert( ChangeLogEntry2->Flags & CHANGELOG_NAME_SPECIFIED );
|
||
|
||
if( (ChangeLogEntry1->Flags & CHANGELOG_NAME_SPECIFIED) == 0 ||
|
||
(ChangeLogEntry2->Flags & CHANGELOG_NAME_SPECIFIED) == 0 ) {
|
||
break;
|
||
}
|
||
|
||
if( _wcsicmp(
|
||
(LPWSTR)((LPBYTE)ChangeLogEntry1 + sizeof(CHANGELOG_ENTRY)),
|
||
(LPWSTR)((LPBYTE)ChangeLogEntry2 + sizeof(CHANGELOG_ENTRY))
|
||
) == 0 ) {
|
||
|
||
return TRUE;
|
||
}
|
||
break;
|
||
|
||
case AddOrChangeLsaPolicy:
|
||
case AddOrChangeDomain:
|
||
return TRUE;
|
||
|
||
default:
|
||
NlPrint((NL_CRITICAL,
|
||
"NlCompareChangeLogEntries: invalid delta type %lx\n",
|
||
ChangeLogEntry1->DeltaType ));
|
||
break;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
PCHANGELOG_ENTRY
|
||
NlGetNextChangeLogEntry(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN LARGE_INTEGER SerialNumber,
|
||
IN DWORD DBIndex,
|
||
OUT LPDWORD ChangeLogEntrySize OPTIONAL
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Search the change log entry in change log cache for a given serial
|
||
number.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer to use.
|
||
|
||
SerialNumber - Serial number preceeding that of the entry to find.
|
||
|
||
DBIndex - Describes which database to find the changelog entry for.
|
||
|
||
ChangeLogEntrySize - Optionally returns the size (in bytes) of the
|
||
returned change log entry.
|
||
|
||
Return Value:
|
||
|
||
Non-NULL - returns a pointer to a duplicate of the found change log entry.
|
||
This buffer must be freed via NetpMemoryFree.
|
||
|
||
NULL - No such entry exists.
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
PCHANGELOG_ENTRY ChangeLogEntry;
|
||
|
||
|
||
//
|
||
// Increment the serial number, get the change log entry, duplicate it
|
||
//
|
||
|
||
LOCK_CHANGELOG();
|
||
SerialNumber.QuadPart += 1;
|
||
ChangeLogEntry = NlFindChangeLogEntry(
|
||
ChangeLogDesc,
|
||
SerialNumber,
|
||
FALSE,
|
||
FALSE,
|
||
DBIndex );
|
||
|
||
if ( ChangeLogEntry != NULL ) {
|
||
ChangeLogEntry = NlDuplicateChangeLogEntry(ChangeLogEntry, ChangeLogEntrySize );
|
||
}
|
||
|
||
UNLOCK_CHANGELOG();
|
||
return ChangeLogEntry;
|
||
}
|
||
|
||
|
||
PCHANGELOG_ENTRY
|
||
NlGetNextUniqueChangeLogEntry(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN LARGE_INTEGER SerialNumber,
|
||
IN DWORD DBIndex,
|
||
OUT LPDWORD ChangeLogEntrySize OPTIONAL
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Search the change log entry in change log cache for a given serial
|
||
number. If there are more than one change log entry for the same
|
||
object then this routine will return the last log entry of that
|
||
object.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer to use.
|
||
|
||
SerialNumber - Serial number preceeding that of the entry to find.
|
||
|
||
DBIndex - Describes which database to find the changelog entry for.
|
||
|
||
ChangeLogEntrySize - Optionally returns the size (in bytes) of the
|
||
returned change log entry.
|
||
|
||
Return Value:
|
||
|
||
Non-NULL - returns a pointer to a duplicate of the found change log entry.
|
||
This buffer must be freed via NetpMemoryFree.
|
||
|
||
NULL - No such entry exists.
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
PCHANGELOG_ENTRY ChangeLogEntry;
|
||
PCHANGELOG_ENTRY NextChangeLogEntry;
|
||
PCHANGELOG_ENTRY FoundChangeLogEntry;
|
||
|
||
|
||
//
|
||
// Get the first entry we want to deal with.
|
||
//
|
||
SerialNumber.QuadPart += 1;
|
||
ChangeLogEntry = NlFindChangeLogEntry(
|
||
ChangeLogDesc,
|
||
SerialNumber,
|
||
FALSE,
|
||
FALSE,
|
||
DBIndex );
|
||
|
||
if ( ChangeLogEntry == NULL ) {
|
||
return NULL;
|
||
}
|
||
|
||
|
||
//
|
||
// Skip over any leading dummy change log entries
|
||
//
|
||
|
||
while ( ChangeLogEntry->DeltaType == DummyChangeLogEntry ) {
|
||
|
||
//
|
||
// Get the next change log entry to compare with.
|
||
//
|
||
|
||
NextChangeLogEntry = NlFindNextChangeLogEntry( ChangeLogDesc,
|
||
ChangeLogEntry,
|
||
DBIndex );
|
||
|
||
if( NextChangeLogEntry == NULL ) {
|
||
return NULL;
|
||
}
|
||
|
||
//
|
||
// skip 'ChangeLogEntry' entry
|
||
//
|
||
|
||
ChangeLogEntry = NextChangeLogEntry;
|
||
}
|
||
|
||
|
||
//
|
||
// Check to see if the next entry is a "duplicate" of this entry.
|
||
//
|
||
|
||
FoundChangeLogEntry = ChangeLogEntry;
|
||
|
||
for (;;) {
|
||
|
||
//
|
||
// Don't walk past a change log entry for a promotion.
|
||
// Promotions don't happen very often, but passing the BDC the
|
||
// change log entry will allow it to do a better job of building
|
||
// its own change log.
|
||
//
|
||
|
||
if ( FoundChangeLogEntry->Flags & CHANGELOG_PDC_PROMOTION ) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Get the next change log entry to compare with.
|
||
//
|
||
|
||
NextChangeLogEntry = NlFindNextChangeLogEntry( ChangeLogDesc,
|
||
ChangeLogEntry,
|
||
DBIndex );
|
||
|
||
if( NextChangeLogEntry == NULL ) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Just skip any dummy entries.
|
||
//
|
||
|
||
if ( NextChangeLogEntry->DeltaType == DummyChangeLogEntry ) {
|
||
ChangeLogEntry = NextChangeLogEntry;
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// if 'FoundChangeLogEntry' and 'NextChangeLogEntry' entries are
|
||
// for different objects or are different delta types.
|
||
// then return 'FoundChangeLogEntry' to the caller.
|
||
//
|
||
|
||
if ( FoundChangeLogEntry->DeltaType != NextChangeLogEntry->DeltaType ||
|
||
!NlCompareChangeLogEntries( FoundChangeLogEntry, NextChangeLogEntry ) ){
|
||
break;
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// Skip 'FoundChangeLogEntry' entry
|
||
// Mark this entry as the being the best one to return.
|
||
//
|
||
|
||
ChangeLogEntry = NextChangeLogEntry;
|
||
FoundChangeLogEntry = ChangeLogEntry;
|
||
}
|
||
|
||
return NlDuplicateChangeLogEntry(FoundChangeLogEntry, ChangeLogEntrySize );
|
||
}
|
||
|
||
|
||
BOOL
|
||
NlRecoverChangeLog(
|
||
PCHANGELOG_ENTRY OrigChangeLogEntry
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine traverses the change log list from current change log entry
|
||
determines whether the current change log can be ignored under
|
||
special conditions.
|
||
|
||
Arguments:
|
||
|
||
OrigChangeLogEntry - pointer to log structure that is under investigation.
|
||
|
||
Return Value:
|
||
|
||
TRUE - if the given change log can be ignored.
|
||
|
||
FALSE - otherwise.
|
||
|
||
--*/
|
||
{
|
||
PCHANGELOG_ENTRY NextChangeLogEntry;
|
||
BOOLEAN ReturnValue;
|
||
|
||
//
|
||
// Find the original change log entry.
|
||
//
|
||
|
||
LOCK_CHANGELOG();
|
||
NextChangeLogEntry = NlFindChangeLogEntry(
|
||
&NlGlobalChangeLogDesc,
|
||
OrigChangeLogEntry->SerialNumber,
|
||
FALSE, // Not downlevel
|
||
FALSE, // Not exact match
|
||
OrigChangeLogEntry->DBIndex );
|
||
|
||
if (NextChangeLogEntry == NULL) {
|
||
ReturnValue = FALSE;
|
||
goto Cleanup;
|
||
}
|
||
|
||
if ( OrigChangeLogEntry->DeltaType >= MAX_DELETE_DELTA ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"NlRecoverChangeLog: invalid delta type %lx\n",
|
||
OrigChangeLogEntry->DeltaType ));
|
||
ReturnValue = FALSE;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Loop for each entry with a greater serial number.
|
||
//
|
||
|
||
for (;;) {
|
||
|
||
NextChangeLogEntry = NlFindNextChangeLogEntry(
|
||
&NlGlobalChangeLogDesc,
|
||
NextChangeLogEntry,
|
||
OrigChangeLogEntry->DBIndex );
|
||
|
||
if (NextChangeLogEntry == NULL) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// If the delta we found is the type that deletes the original delta,
|
||
// and the objects described by the two deltas are the same,
|
||
// tell the caller to not worry about the original delta failing.
|
||
//
|
||
|
||
if ( NextChangeLogEntry->DeltaType ==
|
||
NlGlobalDeleteDeltaType[OrigChangeLogEntry->DeltaType] &&
|
||
NlCompareChangeLogEntries( OrigChangeLogEntry,
|
||
NextChangeLogEntry ) ) {
|
||
ReturnValue = TRUE;
|
||
goto Cleanup;
|
||
}
|
||
|
||
}
|
||
|
||
ReturnValue = FALSE;
|
||
|
||
Cleanup:
|
||
UNLOCK_CHANGELOG();
|
||
return ReturnValue;
|
||
|
||
}
|
||
|
||
|
||
VOID
|
||
NlVoidChangeLogEntry(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN PCHANGELOG_ENTRY ChangeLogEntry,
|
||
IN BOOLEAN FlushIt
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Mark a changelog entry as void. If there are no more change log entries in the file,
|
||
the file is deleted.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer to use.
|
||
|
||
ChangeLogEntry -- Change Log Entry to mark as void.
|
||
|
||
FlushIt - TRUE if the bytes are to be flushed to disk
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
DWORD DBIndex = ChangeLogEntry->DBIndex;
|
||
|
||
|
||
//
|
||
// Mark the changelog entry as being deleted.
|
||
// (and force the change to disk).
|
||
//
|
||
|
||
NlPrint((NL_CHANGELOG,
|
||
"NlVoidChangeLogEntry: %lx %lx: deleting change log entry.\n",
|
||
ChangeLogEntry->SerialNumber.HighPart,
|
||
ChangeLogEntry->SerialNumber.LowPart ));
|
||
|
||
ChangeLogDesc->EntryCount[DBIndex] --;
|
||
|
||
ChangeLogEntry->DBIndex = VOID_DB;
|
||
|
||
(VOID) NlWriteChangeLogBytes(
|
||
ChangeLogDesc,
|
||
&ChangeLogEntry->DBIndex,
|
||
sizeof(ChangeLogEntry->DBIndex),
|
||
FlushIt );
|
||
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
VOID
|
||
NlDeleteChangeLogEntry(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN DWORD DBIndex,
|
||
IN LARGE_INTEGER SerialNumber
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine deletes the change log entry with the particular serial number.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer to use.
|
||
|
||
DBIndex - Describes which database to find the changelog entry for.
|
||
|
||
SerialNumber - Serial number of the entry to find.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
PCHANGELOG_ENTRY ChangeLogEntry;
|
||
|
||
|
||
|
||
//
|
||
// Find the specified change log entry.
|
||
//
|
||
|
||
LOCK_CHANGELOG();
|
||
ChangeLogEntry = NlFindChangeLogEntry(
|
||
ChangeLogDesc,
|
||
SerialNumber,
|
||
FALSE, // Not downlevel
|
||
TRUE, // Exact match
|
||
DBIndex );
|
||
|
||
if (ChangeLogEntry != NULL) {
|
||
|
||
//
|
||
// Mark the changelog entry as being deleted.
|
||
// (and force the change to disk).
|
||
//
|
||
|
||
NlVoidChangeLogEntry( ChangeLogDesc, ChangeLogEntry, TRUE );
|
||
|
||
} else {
|
||
NlPrint((NL_CRITICAL,
|
||
"NlDeleteChangeLogEntry: %lx %lx: couldn't find change log entry.\n",
|
||
SerialNumber.HighPart,
|
||
SerialNumber.LowPart ));
|
||
}
|
||
|
||
UNLOCK_CHANGELOG();
|
||
return;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlCopyChangeLogEntry(
|
||
IN BOOLEAN SourceIsVersion3,
|
||
IN PCHANGELOG_ENTRY SourceChangeLogEntry,
|
||
IN PCHANGELOG_DESCRIPTOR DestChangeLogDesc
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Copies the specified change log entry for the specified "source" change log to
|
||
the specified "destination" change log. The caller is responsible for flushing the
|
||
entry to disk by calling NlFlushChangeLog.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
SourceIsVersion3 - True if the source is a version 3 change log entry
|
||
|
||
SourceChangeLogEntry -- The particular entry to copy
|
||
|
||
DestChangeLogDesc -- a description of the ChangelogBuffer to copy to
|
||
|
||
Return Value:
|
||
|
||
NT Status code
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
CHANGELOG_ENTRY DestChangeLogEntry;
|
||
PSID ObjectSid;
|
||
UNICODE_STRING ObjectNameString;
|
||
PUNICODE_STRING ObjectName;
|
||
|
||
//
|
||
// If this entry has been marked void, ignore it.
|
||
//
|
||
|
||
if ( SourceChangeLogEntry->DBIndex == VOID_DB ) {
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
//
|
||
// Build a version 4 changelog entry from a version 3 one.
|
||
//
|
||
|
||
ObjectSid = NULL;
|
||
ObjectName = NULL;
|
||
|
||
if ( SourceIsVersion3 ) {
|
||
PCHANGELOG_ENTRY_V3 Version3;
|
||
|
||
Version3 = (PCHANGELOG_ENTRY_V3)SourceChangeLogEntry;
|
||
|
||
DestChangeLogEntry.SerialNumber = Version3->SerialNumber;
|
||
DestChangeLogEntry.DeltaType = (BYTE) Version3->DeltaType;
|
||
DestChangeLogEntry.DBIndex = Version3->DBIndex;
|
||
DestChangeLogEntry.ObjectRid = Version3->ObjectRid;
|
||
DestChangeLogEntry.Flags = 0;
|
||
if ( Version3->ObjectSidOffset ) {
|
||
ObjectSid = (PSID)(((LPBYTE)Version3) +
|
||
Version3->ObjectSidOffset);
|
||
}
|
||
if ( Version3->ObjectNameOffset ) {
|
||
RtlInitUnicodeString( &ObjectNameString,
|
||
(LPWSTR)(((LPBYTE)Version3) +
|
||
Version3->ObjectNameOffset));
|
||
ObjectName = &ObjectNameString;
|
||
}
|
||
|
||
//
|
||
// Build a version 4 changelog entry from a version 4 one.
|
||
//
|
||
} else {
|
||
|
||
RtlCopyMemory( &DestChangeLogEntry, SourceChangeLogEntry, sizeof(DestChangeLogEntry) );
|
||
|
||
if ( SourceChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED ) {
|
||
ObjectSid = (PSID)(((LPBYTE)SourceChangeLogEntry) +
|
||
sizeof(CHANGELOG_ENTRY));
|
||
} else if ( SourceChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED ) {
|
||
RtlInitUnicodeString( &ObjectNameString,
|
||
(LPWSTR)(((LPBYTE)SourceChangeLogEntry) +
|
||
sizeof(CHANGELOG_ENTRY)));
|
||
ObjectName = &ObjectNameString;
|
||
}
|
||
|
||
|
||
}
|
||
|
||
|
||
Status = NlWriteChangeLogEntry( DestChangeLogDesc,
|
||
&DestChangeLogEntry,
|
||
ObjectSid,
|
||
ObjectName,
|
||
FALSE ); // Don't flush to disk
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
BOOLEAN
|
||
NlFixChangeLog(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN DWORD DBIndex,
|
||
IN LARGE_INTEGER SerialNumber
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine scans the change log and 'removes' all change log entries
|
||
with a serial number greater than the one specified.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer to use.
|
||
|
||
DBIndex - Describes which database to find the changelog entry for.
|
||
|
||
SerialNumber - Serial number of the entry to find.
|
||
|
||
Return Value:
|
||
|
||
TRUE -- if the entry specied by SerialNumber was found.
|
||
|
||
--*/
|
||
{
|
||
PCHANGELOG_ENTRY ChangeLogEntry;
|
||
BOOLEAN SkipFirstEntry = TRUE;
|
||
|
||
//
|
||
// In all cases,
|
||
// the new serial number of the change log is the one passed in.
|
||
//
|
||
|
||
ChangeLogDesc->SerialNumber[DBIndex] = SerialNumber;
|
||
|
||
//
|
||
// Find the specified change log entry.
|
||
//
|
||
|
||
ChangeLogEntry = NlFindChangeLogEntry(
|
||
ChangeLogDesc,
|
||
SerialNumber,
|
||
FALSE, // Not downlevel
|
||
TRUE, // exact match
|
||
DBIndex );
|
||
|
||
if (ChangeLogEntry == NULL) {
|
||
|
||
//
|
||
// If we can't find the entry,
|
||
// simply start from the beginning and delete all entries for this
|
||
// database.
|
||
//
|
||
|
||
ChangeLogEntry = NlFindFirstChangeLogEntry( ChangeLogDesc, DBIndex );
|
||
SkipFirstEntry = FALSE;
|
||
|
||
if (ChangeLogEntry == NULL) {
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// Loop for each entry with a greater serial number.
|
||
//
|
||
|
||
for (;;) {
|
||
|
||
//
|
||
// Skip past the previous entry.
|
||
//
|
||
// Don't do this the first time if we want to start at the very beginning.
|
||
//
|
||
|
||
if ( SkipFirstEntry ) {
|
||
ChangeLogEntry = NlFindNextChangeLogEntry( ChangeLogDesc,
|
||
ChangeLogEntry,
|
||
DBIndex );
|
||
} else {
|
||
SkipFirstEntry = TRUE;
|
||
}
|
||
|
||
|
||
if (ChangeLogEntry == NULL) {
|
||
break;
|
||
}
|
||
|
||
|
||
//
|
||
// Mark the changelog entry as being deleted.
|
||
// (but don't flush to disk yet).
|
||
//
|
||
|
||
NlVoidChangeLogEntry( ChangeLogDesc, ChangeLogEntry, FALSE );
|
||
|
||
//
|
||
// If deleting the change log entry caused the changelog to be deleted,
|
||
// exit now since 'ChangeLogEntry' points to freed memory.
|
||
//
|
||
|
||
if ( ChangeLogDesc->EntryCount[DBIndex] == 0 ) {
|
||
break;
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Flush all the changes to disk.
|
||
//
|
||
|
||
(VOID) NlFlushChangeLog( ChangeLogDesc );
|
||
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
BOOL
|
||
NlValidateChangeLogEntry(
|
||
IN PCHANGELOG_ENTRY ChangeLogEntry,
|
||
IN DWORD ChangeLogEntrySize
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Validate the a ChangeLogEntry is structurally sound.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogEntry: pointer to a change log entry.
|
||
|
||
ChangeLogEntrySize -- Size (in bytes) of the change log entry not including
|
||
header and trailer.
|
||
|
||
Return Value:
|
||
|
||
TRUE: if the given entry is valid
|
||
|
||
FALSE: otherwise.
|
||
|
||
--*/
|
||
{
|
||
|
||
//
|
||
// Ensure the entry is big enough.
|
||
//
|
||
|
||
if ( ChangeLogEntrySize < sizeof(CHANGELOG_ENTRY) ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NlValidateChangeLogEntry: Entry size is too small: %ld\n",
|
||
ChangeLogEntrySize ));
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Ensure strings are zero terminated.
|
||
//
|
||
|
||
if ( ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED ) {
|
||
|
||
LPWSTR ZeroTerminator = (LPWSTR)(ChangeLogEntry+1);
|
||
BOOLEAN ZeroTerminatorFound = FALSE;
|
||
|
||
if ( ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NlValidateChangeLogEntry: %lx %lx: both Name and Sid specified.\n",
|
||
ChangeLogEntry->SerialNumber.HighPart,
|
||
ChangeLogEntry->SerialNumber.LowPart ));
|
||
return FALSE;
|
||
}
|
||
|
||
while ( (DWORD)((LPBYTE)ZeroTerminator - (LPBYTE) ChangeLogEntry) <
|
||
ChangeLogEntrySize - 1 ) {
|
||
|
||
if ( *ZeroTerminator == L'\0' ) {
|
||
ZeroTerminatorFound = TRUE;
|
||
break;
|
||
}
|
||
ZeroTerminator ++;
|
||
}
|
||
|
||
if ( !ZeroTerminatorFound ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NlValidateChangeLogEntry: %lx %lx: String not zero terminated. (no string)\n",
|
||
ChangeLogEntry->SerialNumber.HighPart,
|
||
ChangeLogEntry->SerialNumber.LowPart ));
|
||
return FALSE;
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Ensure the sid is entirely within the block.
|
||
//
|
||
|
||
if ( ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED ) {
|
||
|
||
if ( GetSidLengthRequired(0) >
|
||
ChangeLogEntrySize - sizeof(*ChangeLogEntry) ||
|
||
RtlLengthSid( (PSID)(ChangeLogEntry+1) ) >
|
||
ChangeLogEntrySize - sizeof(*ChangeLogEntry) ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NlValidateChangeLogEntry: %lx %lx: Sid too large.\n",
|
||
ChangeLogEntry->SerialNumber.HighPart,
|
||
ChangeLogEntry->SerialNumber.LowPart ));
|
||
return FALSE;
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Ensure the database # is valid.
|
||
// ARGH! Allow VOID_DB.
|
||
//
|
||
|
||
if ( ChangeLogEntry->DBIndex > NUM_DBS ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NlValidateChangeLogEntry: %lx %lx: DBIndex is bad %ld.\n",
|
||
ChangeLogEntry->SerialNumber.HighPart,
|
||
ChangeLogEntry->SerialNumber.LowPart,
|
||
ChangeLogEntry->DBIndex ));
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
BOOL
|
||
ValidateThisEntry(
|
||
IN OUT PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN PCHANGELOG_ENTRY ChangeLogEntry,
|
||
IN OUT PLARGE_INTEGER NextSerialNumber,
|
||
IN BOOLEAN InitialCall
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Determine the given log entry is a valid next log in the change log
|
||
list.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer to validate.
|
||
|
||
ChangeLogEntry: pointer to a new log entry.
|
||
|
||
NextSerialNumber: pointer to an array of serial numbers.
|
||
(NULL if serial numbers aren't to be validated.)
|
||
|
||
Initialcall: TRUE iff SerialNumber array should be initialized.
|
||
|
||
Return Value:
|
||
|
||
TRUE: if the given entry is a valid next entry.
|
||
|
||
FALSE: otherwise.
|
||
|
||
Assumed: non-empty ChangeLog list.
|
||
|
||
--*/
|
||
{
|
||
PCHANGELOG_BLOCK_HEADER Block = ((PCHANGELOG_BLOCK_HEADER)ChangeLogEntry) - 1;
|
||
|
||
//
|
||
// Do Version 3 specific things
|
||
//
|
||
|
||
if ( ChangeLogDesc->Version3 ) {
|
||
|
||
//
|
||
// Ensure the block is big enough.
|
||
//
|
||
|
||
if ( Block->BlockSize <
|
||
sizeof(CHANGELOG_ENTRY_V3) + sizeof(CHANGELOG_BLOCK_HEADER) ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"ValidateThisEntry: Block size is too small: %ld\n",
|
||
Block->BlockSize ));
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Ensure the database # is valid.
|
||
//
|
||
|
||
if ( ChangeLogEntry->DBIndex > NUM_DBS ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"ValidateThisEntry: %lx %lx: DBIndex is bad %ld.\n",
|
||
ChangeLogEntry->SerialNumber.HighPart,
|
||
ChangeLogEntry->SerialNumber.LowPart,
|
||
ChangeLogEntry->DBIndex ));
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
//
|
||
// Do version 4 specific validation
|
||
//
|
||
|
||
} else {
|
||
|
||
//
|
||
// Ensure the block is big enough.
|
||
//
|
||
|
||
if ( Block->BlockSize <
|
||
sizeof(CHANGELOG_BLOCK_HEADER) +
|
||
sizeof(CHANGELOG_ENTRY) +
|
||
sizeof(CHANGELOG_BLOCK_TRAILER) ) {
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"ValidateThisEntry: Block size is too small: %ld\n",
|
||
Block->BlockSize ));
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
//
|
||
// Validate the contents of the block itself.
|
||
//
|
||
|
||
if ( !NlValidateChangeLogEntry(
|
||
ChangeLogEntry,
|
||
Block->BlockSize -
|
||
sizeof(CHANGELOG_BLOCK_HEADER) -
|
||
sizeof(CHANGELOG_BLOCK_TRAILER) ) ) {
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// Validate the serial number sequence.
|
||
//
|
||
|
||
if ( ChangeLogEntry->DBIndex != VOID_DB && NextSerialNumber != NULL ) {
|
||
|
||
//
|
||
// If this is the first entry in the database,
|
||
// Save its serial number.
|
||
//
|
||
|
||
if ( NextSerialNumber[ChangeLogEntry->DBIndex].QuadPart == 0 ) {
|
||
|
||
//
|
||
// first entry for this database
|
||
//
|
||
|
||
NextSerialNumber[ChangeLogEntry->DBIndex] = ChangeLogEntry->SerialNumber;
|
||
|
||
|
||
//
|
||
// Otherwise ensure the serial number is the value expected.
|
||
//
|
||
|
||
} else {
|
||
|
||
if ( !IsSerialNumberEqual(
|
||
ChangeLogDesc,
|
||
ChangeLogEntry,
|
||
&NextSerialNumber[ChangeLogEntry->DBIndex] )){
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"ValidateThisEntry: %lx %lx: Serial number is bad. s.b. %lx %lx\n",
|
||
ChangeLogEntry->SerialNumber.HighPart,
|
||
ChangeLogEntry->SerialNumber.LowPart,
|
||
NextSerialNumber[ChangeLogEntry->DBIndex].HighPart,
|
||
NextSerialNumber[ChangeLogEntry->DBIndex].LowPart ));
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Increment next expected serial number
|
||
//
|
||
|
||
NextSerialNumber[ChangeLogEntry->DBIndex].QuadPart =
|
||
ChangeLogEntry->SerialNumber.QuadPart + 1;
|
||
|
||
|
||
//
|
||
// The current entry specifies the highest serial number for its
|
||
// database.
|
||
//
|
||
|
||
if ( InitialCall ) {
|
||
ChangeLogDesc->SerialNumber[ChangeLogEntry->DBIndex] =
|
||
ChangeLogEntry->SerialNumber;
|
||
ChangeLogDesc->EntryCount[ChangeLogEntry->DBIndex] ++;
|
||
}
|
||
|
||
}
|
||
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
BOOL
|
||
ValidateBlock(
|
||
IN OUT PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN PCHANGELOG_BLOCK_HEADER Block,
|
||
IN OUT LARGE_INTEGER *NextSerialNumber,
|
||
IN BOOLEAN InitialCall
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Validate a changelog block.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer to validate.
|
||
|
||
Block: pointer to the change log block to validate
|
||
|
||
NextSerialNumber: pointer to an array of serial numbers.
|
||
(NULL if serial numbers aren't to be validated.)
|
||
|
||
InitializeCall: TRUE iff SerialNumber array should be initialized.
|
||
|
||
Return Value:
|
||
|
||
TRUE: if the given entry is a valid next entry.
|
||
|
||
FALSE: otherwise.
|
||
|
||
--*/
|
||
{
|
||
//
|
||
// Ensure Block size is properly aligned.
|
||
//
|
||
|
||
if ( Block->BlockSize != ROUND_UP_COUNT(Block->BlockSize, ALIGN_WORST) ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"ValidateBlock: Block size alignment is bad.\n" ));
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
//
|
||
// Ensure the block is contained in the cache.
|
||
//
|
||
|
||
if ( Block->BlockSize > ChangeLogDesc->BufferSize ||
|
||
((LPBYTE)Block + Block->BlockSize) > ChangeLogDesc->BufferEnd ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"ValidateBlock: Block extends beyond end of buffer.\n" ));
|
||
return FALSE;
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// Do Version 3 specific things
|
||
//
|
||
|
||
if ( ChangeLogDesc->Version3 ) {
|
||
|
||
//
|
||
// Ensure the block is big enough.
|
||
//
|
||
|
||
if ( Block->BlockSize < sizeof(CHANGELOG_BLOCK_HEADER) ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"ValidateBlock: Block size is too small: %ld\n",
|
||
Block->BlockSize ));
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
//
|
||
// Do version 4 specific validation
|
||
//
|
||
|
||
} else {
|
||
|
||
//
|
||
// Ensure the block is big enough.
|
||
//
|
||
|
||
if ( Block->BlockSize <
|
||
sizeof(CHANGELOG_BLOCK_HEADER) +
|
||
sizeof(CHANGELOG_BLOCK_TRAILER) ) {
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"ValidateBlock: Block size is too small: %ld\n",
|
||
Block->BlockSize ));
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Ensure trailer and header match
|
||
//
|
||
|
||
if ( ChangeLogBlockTrailer(Block)->BlockSize != Block->BlockSize ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"ValidateBlock: Header/Trailer block size mismatch: %ld %ld (Trailer fixed).\n",
|
||
Block->BlockSize,
|
||
ChangeLogBlockTrailer(Block)->BlockSize ));
|
||
ChangeLogBlockTrailer(Block)->BlockSize = Block->BlockSize;
|
||
}
|
||
|
||
|
||
}
|
||
|
||
//
|
||
// Free blocks have no other checking to do
|
||
//
|
||
switch ( Block->BlockState ) {
|
||
case BlockFree:
|
||
|
||
break;
|
||
|
||
//
|
||
// Used blocks have more checking to do.
|
||
//
|
||
|
||
case BlockUsed:
|
||
|
||
if ( !ValidateThisEntry( ChangeLogDesc,
|
||
(PCHANGELOG_ENTRY)(Block+1),
|
||
NextSerialNumber,
|
||
InitialCall )) {
|
||
return FALSE;
|
||
}
|
||
break;
|
||
|
||
|
||
//
|
||
// The hole is allowed only at the end of the buffer.
|
||
//
|
||
|
||
case BlockHole:
|
||
if ( (LPBYTE)Block + Block->BlockSize != ChangeLogDesc->BufferEnd ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"ValidateBlock: Hole block in middle of buffer (buffer truncated).\n" ));
|
||
Block->BlockSize = (ULONG)(ChangeLogDesc->BufferEnd - (LPBYTE)Block);
|
||
}
|
||
break;
|
||
|
||
default:
|
||
NlPrint((NL_CRITICAL,
|
||
"ValidateBlock: Invalid block type %ld.\n",
|
||
Block->BlockState ));
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
BOOL
|
||
ValidateList(
|
||
IN OUT PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN BOOLEAN InitialCall
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Determine the given header is a valid header. It is done by
|
||
traversing the circular buffer starting from the given header and
|
||
validate each entry.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer to validate.
|
||
|
||
InitialCall: TRUE iff SerialNumber Array and EntryCount should
|
||
be initialized.
|
||
|
||
Return Value:
|
||
|
||
TRUE: if the given header is valid.
|
||
|
||
FALSE: otherwise
|
||
|
||
|
||
--*/
|
||
{
|
||
|
||
LARGE_INTEGER NextSerialNumber[NUM_DBS];
|
||
PCHANGELOG_BLOCK_HEADER ChangeLogBlock;
|
||
DWORD j;
|
||
|
||
//
|
||
// setup a NextSerialNumber array first.
|
||
//
|
||
|
||
for( j = 0; j < NUM_DBS; j++ ) {
|
||
|
||
NextSerialNumber[j].QuadPart = 0;
|
||
|
||
if ( InitialCall ) {
|
||
ChangeLogDesc->SerialNumber[j].QuadPart = 0;
|
||
}
|
||
}
|
||
|
||
//
|
||
// The cache is valid if it is empty.
|
||
//
|
||
|
||
if ( ChangeLogIsEmpty(ChangeLogDesc) ) {
|
||
return TRUE;
|
||
}
|
||
|
||
//
|
||
// Validate each block
|
||
//
|
||
|
||
for ( ChangeLogBlock = ChangeLogDesc->Head;
|
||
;
|
||
ChangeLogBlock = NlMoveToNextChangeLogBlock( ChangeLogDesc, ChangeLogBlock) ) {
|
||
|
||
//
|
||
// Validate the block.
|
||
//
|
||
|
||
if( !ValidateBlock( ChangeLogDesc,
|
||
ChangeLogBlock,
|
||
NextSerialNumber,
|
||
InitialCall) ) {
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Stop when we get to the end.
|
||
//
|
||
if ( ChangeLogBlock->BlockState == BlockFree ) {
|
||
break;
|
||
}
|
||
|
||
}
|
||
|
||
return TRUE;
|
||
|
||
}
|
||
|
||
|
||
BOOL
|
||
InitChangeLogHeadAndTail(
|
||
IN OUT PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN BOOLEAN NewChangeLog
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function initializes the global head and tail pointers of change
|
||
log block list. The change log cache is made up of variable length
|
||
blocks, each block has a header containing the length of the block
|
||
and the block state ( BlockFree, BlockUsed and BlockHole ). The
|
||
last block in the change log block list is always the free block,
|
||
all other blocks in the cache are used blocks except a block at the
|
||
end of the cache may be a unused block known as 'hole' block. So
|
||
the head of the change log block list is the block that is just next
|
||
to the free block and the tail is the free block.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer to analyze.
|
||
On entry, Buffer and BufferSize describe the allocated block containing
|
||
the change log read from disk.
|
||
On TRUE return, all the fields are filled in.
|
||
|
||
NewChangeLog -- True if no entries are in the change log
|
||
|
||
Return Value:
|
||
|
||
TRUE: if valid head and tail are successfully initialized.
|
||
|
||
FALSE: if valid head and tail can't be determined. This may be due
|
||
to the corrupted change log file.
|
||
|
||
--*/
|
||
{
|
||
PCHANGELOG_BLOCK_HEADER Block;
|
||
PCHANGELOG_BLOCK_HEADER FreeBlock;
|
||
DWORD i;
|
||
|
||
ChangeLogDesc->BufferEnd =
|
||
ChangeLogDesc->Buffer + ChangeLogDesc->BufferSize;
|
||
|
||
//
|
||
// Compute the address of the first physical cache entry.
|
||
//
|
||
ChangeLogDesc->FirstBlock = (PCHANGELOG_BLOCK_HEADER)
|
||
(ChangeLogDesc->Buffer +
|
||
sizeof(CHANGELOG_SIG));
|
||
|
||
ChangeLogDesc->FirstBlock = (PCHANGELOG_BLOCK_HEADER)
|
||
ROUND_UP_POINTER ( ChangeLogDesc->FirstBlock, ALIGN_WORST );
|
||
|
||
//
|
||
// Clear the count of entries in the change log and the serial numbers
|
||
// (We'll compute them later when we call ValidateList().)
|
||
|
||
for( i = 0; i < NUM_DBS; i++ ) {
|
||
ChangeLogDesc->EntryCount[i] = 0;
|
||
ChangeLogDesc->SerialNumber[i].QuadPart = 0;
|
||
}
|
||
|
||
|
||
//
|
||
// If this is a new change log,
|
||
// Initialize the Change Log Cache to zero.
|
||
//
|
||
|
||
Block = ChangeLogDesc->FirstBlock;
|
||
|
||
if ( NewChangeLog ) {
|
||
|
||
RtlZeroMemory(ChangeLogDesc->Buffer, ChangeLogDesc->BufferSize);
|
||
(VOID) strcpy( (PCHAR)ChangeLogDesc->Buffer, CHANGELOG_SIG);
|
||
|
||
Block->BlockState = BlockFree;
|
||
|
||
Block->BlockSize =
|
||
(ULONG)(ChangeLogDesc->BufferEnd - (LPBYTE)ChangeLogDesc->FirstBlock);
|
||
ChangeLogBlockTrailer(Block)->BlockSize = Block->BlockSize;
|
||
|
||
ChangeLogDesc->Version3 = FALSE;
|
||
ChangeLogDesc->Head = ChangeLogDesc->Tail = ChangeLogDesc->FirstBlock;
|
||
return TRUE;
|
||
}
|
||
|
||
//
|
||
// If no entries have been written to the changelog,
|
||
// simply initialize the head and tail to the block start.
|
||
//
|
||
|
||
if ( ChangeLogIsEmpty( ChangeLogDesc ) ) {
|
||
|
||
ChangeLogDesc->Head = ChangeLogDesc->Tail = ChangeLogDesc->FirstBlock;
|
||
|
||
NlPrint((NL_CHANGELOG,
|
||
"InitChangeLogHeadAndTail: Change log is empty.\n" ));
|
||
return TRUE;
|
||
}
|
||
|
||
//
|
||
// Loop through the cache looking for a free block.
|
||
//
|
||
|
||
FreeBlock = NULL;
|
||
|
||
do {
|
||
|
||
//
|
||
// Validate the block's integrity.
|
||
//
|
||
|
||
if ( !ValidateBlock( ChangeLogDesc, Block, NULL, FALSE )) {
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Just remember where the free block is.
|
||
//
|
||
|
||
if ( Block->BlockState == BlockFree ) {
|
||
|
||
if ( FreeBlock != NULL ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"InitChangeLogHeadAndTail: Multiple free blocks found.\n" ));
|
||
return FALSE;
|
||
}
|
||
|
||
FreeBlock = Block;
|
||
}
|
||
|
||
//
|
||
// Move to next block
|
||
//
|
||
|
||
Block = (PCHANGELOG_BLOCK_HEADER) ((LPBYTE)Block + Block->BlockSize);
|
||
|
||
} while ( (LPBYTE)Block < ChangeLogDesc->BufferEnd );
|
||
|
||
//
|
||
// If we didn't find a free block,
|
||
// the changelog is corrupt.
|
||
//
|
||
|
||
if ( FreeBlock == NULL ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"InitChangeLogHeadAndTail: No Free block anywhere in buffer.\n" ));
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// We found the free block.
|
||
// (The tail pointer always points to the free block.)
|
||
//
|
||
|
||
ChangeLogDesc->Tail = FreeBlock;
|
||
|
||
//
|
||
// If free block is the last block in the change log block
|
||
// list, the head of the list is the first block in
|
||
// the list.
|
||
//
|
||
if( ((LPBYTE)FreeBlock + FreeBlock->BlockSize) >=
|
||
ChangeLogDesc->BufferEnd ) {
|
||
|
||
ChangeLogDesc->Head = ChangeLogDesc->FirstBlock;
|
||
|
||
//
|
||
//
|
||
// Otherwise, the head of the list is immediately after the tail.
|
||
//
|
||
|
||
} else {
|
||
|
||
ChangeLogDesc->Head = (PCHANGELOG_BLOCK_HEADER)
|
||
((LPBYTE)FreeBlock + FreeBlock->BlockSize);
|
||
}
|
||
|
||
|
||
//
|
||
// Validate the list before returning from here.
|
||
//
|
||
|
||
if ( !ValidateList( ChangeLogDesc, TRUE) ) {
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlResetChangeLog(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN DWORD NewChangeLogSize
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function resets the change log cache and change log file. This
|
||
function is called from InitChangeLog() function to afresh the
|
||
change log. This function may also be called from
|
||
I_NetNotifyDelta() function when the serial number of the new entry
|
||
is out of order.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer being used
|
||
|
||
NewChangeLogSize -- Size (in bytes) of the new change log.
|
||
|
||
Return Value:
|
||
|
||
NT Status code
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
|
||
NlPrint((NL_CHANGELOG, "%s log being reset.\n",
|
||
ChangeLogDesc->TempLog ? "TempChange" : "Change" ));
|
||
|
||
//
|
||
// Start with a clean slate.
|
||
//
|
||
|
||
NlCloseChangeLogFile( ChangeLogDesc );
|
||
|
||
//
|
||
// Allocate a buffer.
|
||
//
|
||
|
||
ChangeLogDesc->BufferSize = NewChangeLogSize;
|
||
|
||
ChangeLogDesc->Buffer = NetpMemoryAllocate(ChangeLogDesc->BufferSize );
|
||
|
||
if ( ChangeLogDesc->Buffer == NULL ) {
|
||
return STATUS_NO_MEMORY;
|
||
}
|
||
|
||
|
||
//
|
||
// Initialize the Change Log Cache to zero.
|
||
//
|
||
|
||
(VOID) InitChangeLogHeadAndTail( ChangeLogDesc, TRUE );
|
||
|
||
//
|
||
// Write the cache to the file.
|
||
//
|
||
|
||
Status = NlWriteChangeLogBytes( ChangeLogDesc,
|
||
ChangeLogDesc->Buffer,
|
||
ChangeLogDesc->BufferSize,
|
||
TRUE ); // Flush the bytes to disk
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
I_NetLogonReadChangeLog(
|
||
IN PVOID InContext,
|
||
IN ULONG InContextSize,
|
||
IN ULONG ChangeBufferSize,
|
||
OUT PVOID *ChangeBuffer,
|
||
OUT PULONG BytesRead,
|
||
OUT PVOID *OutContext,
|
||
OUT PULONG OutContextSize
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function returns a portion of the change log to the caller.
|
||
|
||
The caller asks for the first portion of the change log by passing zero as
|
||
the InContext/InContextSize. Each call passes out an OutContext that
|
||
indentifies the last change returned to the caller. That context can
|
||
be passed in on a subsequent call to I_NetlogonReadChangeLog.
|
||
|
||
Arguments:
|
||
|
||
InContext - Opaque context describing the last entry to have been previously
|
||
returned. Specify NULL to request the first entry.
|
||
|
||
InContextSize - Size (in bytes) of InContext. Specify 0 to request the
|
||
first entry.
|
||
|
||
ChangeBufferSize - Specifies the size (in bytes) of the passed in ChangeBuffer.
|
||
|
||
ChangeBuffer - Returns the next several entries from the change log.
|
||
Buffer must be DWORD aligned.
|
||
|
||
BytesRead - Returns the size (in bytes) of the entries returned in ChangeBuffer.
|
||
|
||
OutContext - Returns an opaque context describing the last entry returned
|
||
in ChangeBuffer. NULL is returned if no entries were returned.
|
||
The buffer must be freed using I_NetLogonFree
|
||
|
||
OutContextSize - Returns the size (in bytes) of OutContext.
|
||
|
||
|
||
Return Value:
|
||
|
||
STATUS_MORE_ENTRIES - More entries are available. This function should
|
||
be called again to retrieve the remaining entries.
|
||
|
||
STATUS_SUCCESS - No more entries are currently available. Some entries may
|
||
have been returned on this call. This function need not be called again.
|
||
However, the caller can determine if new change log entries were
|
||
added to the log, by calling this function again passing in the returned
|
||
context.
|
||
|
||
STATUS_INVALID_PARAMETER - InContext is invalid.
|
||
Either it is too short or the change log entry described no longer
|
||
exists in the change log.
|
||
|
||
STATUS_INVALID_DOMAIN_ROLE - Change log not initialized
|
||
|
||
STATUS_NO_MEMORY - There is not enough memory to allocate OutContext.
|
||
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
CHANGELOG_CONTEXT Context;
|
||
PCHANGELOG_ENTRY ChangeLogEntry;
|
||
ULONG BytesCopied = 0;
|
||
LPBYTE Where = (LPBYTE)ChangeBuffer;
|
||
ULONG EntriesCopied = 0;
|
||
|
||
//
|
||
// Initialization.
|
||
//
|
||
|
||
*OutContext = NULL;
|
||
*OutContextSize = 0;
|
||
|
||
//
|
||
// Ensure the role is right. Otherwise, all the globals used below
|
||
// aren't initialized.
|
||
//
|
||
|
||
if ( NlGlobalChangeLogRole != ChangeLogPrimary ) {
|
||
NlPrint((NL_CHANGELOG,
|
||
"I_NetLogonReadChangeLog: failed 1\n" ));
|
||
return STATUS_INVALID_DOMAIN_ROLE;
|
||
}
|
||
|
||
//
|
||
// Also make sure that the change log cache is available.
|
||
//
|
||
|
||
if ( NlGlobalChangeLogDesc.Buffer == NULL ) {
|
||
NlPrint((NL_CHANGELOG,
|
||
"I_NetLogonReadChangeLog: failed 2\n" ));
|
||
return STATUS_INVALID_DOMAIN_ROLE;
|
||
}
|
||
|
||
//
|
||
// Validate the context.
|
||
//
|
||
|
||
LOCK_CHANGELOG();
|
||
if ( InContext == NULL ) {
|
||
NlPrint((NL_CHANGELOG, "I_NetLogonReadChangeLog: called with NULL\n" ));
|
||
|
||
//
|
||
// Start the sequence number at one.
|
||
//
|
||
|
||
Context.SequenceNumber = 1;
|
||
|
||
//
|
||
// If nothing has ever been written to the change log,
|
||
// indicate nothing is available.
|
||
//
|
||
|
||
if ( ChangeLogIsEmpty( &NlGlobalChangeLogDesc ) ) {
|
||
ChangeLogEntry = NULL;
|
||
|
||
//
|
||
// Otherwise, start at the beginning of the log.
|
||
//
|
||
} else {
|
||
ChangeLogEntry = (PCHANGELOG_ENTRY) (NlGlobalChangeLogDesc.Head + 1);
|
||
}
|
||
|
||
|
||
} else {
|
||
|
||
//
|
||
// Ensure the context wasn't mangled.
|
||
//
|
||
|
||
if ( InContextSize < sizeof(CHANGELOG_CONTEXT) ) {
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
goto Cleanup;
|
||
}
|
||
|
||
RtlCopyMemory( &Context, InContext, sizeof(CHANGELOG_CONTEXT) );
|
||
|
||
|
||
NlPrint((NL_CHANGELOG, "I_NetLogonReadChangeLog: called with %lx %lx in %ld (%ld)\n",
|
||
Context.SerialNumber.HighPart,
|
||
Context.SerialNumber.LowPart,
|
||
Context.DbIndex,
|
||
Context.SequenceNumber ));
|
||
|
||
//
|
||
// Increment the sequence number.
|
||
//
|
||
|
||
Context.SequenceNumber++;
|
||
|
||
//
|
||
// Find the change log entry corresponding to the context.
|
||
//
|
||
|
||
ChangeLogEntry = NlFindChangeLogEntry( &NlGlobalChangeLogDesc,
|
||
Context.SerialNumber,
|
||
FALSE, // Not downlevel
|
||
TRUE, // Exact match needed
|
||
Context.DbIndex );
|
||
|
||
if ( ChangeLogEntry == NULL ) {
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
|
||
NlPrint((NL_CHANGELOG, "I_NetLogonReadChangeLog: %lx %lx in %ld: Entry no longer exists in change log.\n",
|
||
Context.SerialNumber.HighPart,
|
||
Context.SerialNumber.LowPart,
|
||
Context.DbIndex ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// The next change log entry is the one to return first.
|
||
//
|
||
|
||
ChangeLogEntry = NlMoveToNextChangeLogEntry( &NlGlobalChangeLogDesc,
|
||
ChangeLogEntry );
|
||
}
|
||
|
||
//
|
||
// Copy a header into the ChangeBuffer.
|
||
//
|
||
|
||
if ( ChangeBufferSize <= sizeof(CHANGELOG_BUFFER_HEADER) ) {
|
||
Status = STATUS_BUFFER_TOO_SMALL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
((PCHANGELOG_BUFFER_HEADER)Where)->Size = sizeof(CHANGELOG_BUFFER_HEADER);
|
||
((PCHANGELOG_BUFFER_HEADER)Where)->Version = CHANGELOG_BUFFER_VERSION;
|
||
((PCHANGELOG_BUFFER_HEADER)Where)->SequenceNumber = Context.SequenceNumber;
|
||
((PCHANGELOG_BUFFER_HEADER)Where)->Flags = 0;
|
||
|
||
Where += sizeof(CHANGELOG_BUFFER_HEADER);
|
||
BytesCopied += sizeof(CHANGELOG_BUFFER_HEADER);
|
||
|
||
|
||
|
||
|
||
//
|
||
// Loop returning change log entries to the caller.
|
||
//
|
||
// ASSERT: ChangeLogEntry is the next entry to return to the caller.
|
||
// ASSERT: ChangeLogEntry is NULL if there are no more change log entries.
|
||
|
||
while ( ChangeLogEntry != NULL ) {
|
||
ULONG SizeToCopy;
|
||
PCHANGELOG_BLOCK_HEADER ChangeLogBlock;
|
||
|
||
//
|
||
// Skip over any voided log entries.
|
||
//
|
||
|
||
while ( ChangeLogEntry != NULL && ChangeLogEntry->DBIndex == VOID_DB ) {
|
||
|
||
//
|
||
// Get the next entry to copy.
|
||
//
|
||
|
||
ChangeLogEntry = NlMoveToNextChangeLogEntry( &NlGlobalChangeLogDesc,
|
||
ChangeLogEntry );
|
||
|
||
}
|
||
|
||
if ( ChangeLogEntry == NULL ) {
|
||
break;
|
||
}
|
||
|
||
|
||
//
|
||
// Compute the size of the change log entry to copy.
|
||
//
|
||
|
||
ChangeLogBlock = (PCHANGELOG_BLOCK_HEADER)
|
||
( (LPBYTE) ChangeLogEntry - sizeof(CHANGELOG_BLOCK_HEADER) );
|
||
|
||
SizeToCopy = ChangeLogBlock->BlockSize -
|
||
sizeof(CHANGELOG_BLOCK_HEADER) -
|
||
sizeof(CHANGELOG_BLOCK_TRAILER);
|
||
NlAssert( SizeToCopy == ROUND_UP_COUNT( SizeToCopy, ALIGN_DWORD ));
|
||
|
||
//
|
||
// Ensure the entry fits in the buffer.
|
||
//
|
||
|
||
if ( BytesCopied + SizeToCopy + sizeof(DWORD) > ChangeBufferSize ) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Copy this entry into the buffer.
|
||
//
|
||
|
||
*((LPDWORD)Where) = SizeToCopy;
|
||
Where += sizeof(DWORD);
|
||
BytesCopied += sizeof(DWORD);
|
||
|
||
RtlCopyMemory( Where, ChangeLogEntry, SizeToCopy );
|
||
Where += SizeToCopy;
|
||
BytesCopied += SizeToCopy;
|
||
EntriesCopied += 1;
|
||
|
||
//
|
||
// Remember the context of this Entry.
|
||
//
|
||
|
||
Context.SerialNumber.QuadPart = ChangeLogEntry->SerialNumber.QuadPart;
|
||
Context.DbIndex = ChangeLogEntry->DBIndex;
|
||
|
||
//
|
||
// Get the next entry to copy.
|
||
//
|
||
|
||
ChangeLogEntry = NlMoveToNextChangeLogEntry( &NlGlobalChangeLogDesc,
|
||
ChangeLogEntry );
|
||
|
||
}
|
||
|
||
//
|
||
// Determine the status code to return.
|
||
//
|
||
|
||
if ( ChangeLogEntry == NULL ) {
|
||
Status = STATUS_SUCCESS;
|
||
} else {
|
||
Status = STATUS_MORE_ENTRIES;
|
||
|
||
//
|
||
// If no data was copied,
|
||
// give a better status.
|
||
//
|
||
|
||
if ( EntriesCopied == 0 ) {
|
||
Status = STATUS_BUFFER_TOO_SMALL;
|
||
BytesCopied = 0;
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
//
|
||
// If any data was returned,
|
||
// return context to the caller.
|
||
//
|
||
|
||
if ( EntriesCopied ) {
|
||
|
||
*OutContext = NetpMemoryAllocate( sizeof(CHANGELOG_CONTEXT) );
|
||
|
||
if ( *OutContext == NULL ) {
|
||
Status = STATUS_NO_MEMORY;
|
||
BytesCopied = 0;
|
||
goto Cleanup;
|
||
}
|
||
|
||
*((PCHANGELOG_CONTEXT)*OutContext) = Context;
|
||
*OutContextSize = sizeof(CHANGELOG_CONTEXT);
|
||
|
||
}
|
||
|
||
|
||
Cleanup:
|
||
*BytesRead = BytesCopied;
|
||
UNLOCK_CHANGELOG();
|
||
return Status;
|
||
}
|
||
|
||
|
||
|
||
|
||
NTSTATUS
|
||
I_NetLogonNewChangeLog(
|
||
OUT HANDLE *ChangeLogHandle
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function opens a new changelog file for writing. The new changelog
|
||
is a temporary file. The real change will not be modified until
|
||
I_NetLogonCloseChangeLog is called asking to Comit the changes.
|
||
|
||
The caller should follow this call by Zero more calls to
|
||
I_NetLogonAppendChangeLog followed by a call to I_NetLogonCloseChangeLog.
|
||
|
||
Only one temporary change log can be active at once.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogHandle - Returns a handle identifying the temporary change log.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS - The temporary change log has been successfully opened.
|
||
|
||
STATUS_INVALID_DOMAIN_ROLE - DC is neither PDC nor BDC.
|
||
|
||
STATUS_NO_MEMORY - Not enough memory to create the change log buffer.
|
||
|
||
Sundry file creation errors.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
|
||
NlPrint((NL_CHANGELOG, "I_NetLogonNewChangeLog: called\n" ));
|
||
|
||
//
|
||
// Ensure the role is right. Otherwise, all the globals used below
|
||
// aren't initialized.
|
||
//
|
||
|
||
if ( NlGlobalChangeLogRole != ChangeLogPrimary &&
|
||
NlGlobalChangeLogRole != ChangeLogBackup ) {
|
||
NlPrint((NL_CHANGELOG,
|
||
"I_NetLogonNewChangeLog: failed 1\n" ));
|
||
return STATUS_INVALID_DOMAIN_ROLE;
|
||
}
|
||
|
||
//
|
||
// Initialize the global context.
|
||
//
|
||
|
||
LOCK_CHANGELOG();
|
||
InitChangeLogDesc( &NlGlobalTempChangeLogDesc );
|
||
NlGlobalTempChangeLogDesc.TempLog = TRUE;
|
||
|
||
//
|
||
// Create a temporary change log the same size as the real changelog
|
||
//
|
||
|
||
Status = NlResetChangeLog( &NlGlobalTempChangeLogDesc,
|
||
NlGlobalChangeLogDesc.BufferSize );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrint((NL_CHANGELOG,
|
||
"I_NetLogonNewChangeLog: cannot reset temp change log 0x%lx\n",
|
||
Status ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
NlGlobalChangeLogSequenceNumber = 0;
|
||
*(PULONG)ChangeLogHandle = ++ NlGlobalChangeLogHandle;
|
||
Cleanup:
|
||
UNLOCK_CHANGELOG();
|
||
return Status;
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
NTSTATUS
|
||
I_NetLogonAppendChangeLog(
|
||
IN HANDLE ChangeLogHandle,
|
||
IN PVOID ChangeBuffer,
|
||
IN ULONG ChangeBufferSize
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function appends change log information to new changelog file.
|
||
|
||
The ChangeBuffer must be a change buffer returned from I_NetLogonReadChangeLog.
|
||
Care should be taken to ensure each call to I_NetLogonReadChangeLog is
|
||
exactly matched by one call to I_NetLogonAppendChangeLog.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogHandle - A handle identifying the temporary change log.
|
||
|
||
ChangeBuffer - A buffer describing a set of changes returned from
|
||
I_NetLogonReadChangeLog.
|
||
|
||
ChangeBufferSize - Size (in bytes) of ChangeBuffer.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS - The temporary change log has been successfully opened.
|
||
|
||
STATUS_INVALID_DOMAIN_ROLE - DC is neither PDC nor BDC.
|
||
|
||
STATUS_INVALID_HANDLE - ChangeLogHandle is not valid.
|
||
|
||
STATUS_INVALID_PARAMETER - ChangeBuffer contains invalid data.
|
||
|
||
Sundry disk write errors.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
LPBYTE Where;
|
||
ULONG BytesLeft;
|
||
LPBYTE AllocatedChangeBuffer = NULL;
|
||
|
||
//
|
||
// Ensure the role is right. Otherwise, all the globals used below
|
||
// aren't initialized.
|
||
//
|
||
|
||
if ( NlGlobalChangeLogRole != ChangeLogPrimary &&
|
||
NlGlobalChangeLogRole != ChangeLogBackup ) {
|
||
NlPrint((NL_CHANGELOG,
|
||
"I_NetLogonAppendChangeLog: failed 1\n" ));
|
||
return STATUS_INVALID_DOMAIN_ROLE;
|
||
}
|
||
|
||
//
|
||
// Check the handle.
|
||
//
|
||
|
||
LOCK_CHANGELOG();
|
||
if ( HandleToUlong(ChangeLogHandle) != NlGlobalChangeLogHandle ) {
|
||
Status = STATUS_INVALID_HANDLE;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Make a properly aligned copy of the buffer.
|
||
// Sam gets it as a byte array over RPC. RPC chooses to align it poorly.
|
||
//
|
||
|
||
BytesLeft = ChangeBufferSize;
|
||
|
||
if ( BytesLeft < sizeof(CHANGELOG_BUFFER_HEADER) ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"I_NetLogonAppendChangeLog: Buffer has no header %ld\n", BytesLeft ));
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
goto Cleanup;
|
||
}
|
||
|
||
AllocatedChangeBuffer = LocalAlloc( 0, BytesLeft );
|
||
|
||
if ( AllocatedChangeBuffer == NULL ) {
|
||
Status = STATUS_NO_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
RtlCopyMemory( AllocatedChangeBuffer, ChangeBuffer, BytesLeft );
|
||
|
||
|
||
|
||
//
|
||
// Validate the buffer header.
|
||
//
|
||
|
||
Where = (LPBYTE) AllocatedChangeBuffer;
|
||
|
||
if ( ((PCHANGELOG_BUFFER_HEADER)Where)->Size < sizeof(CHANGELOG_BUFFER_HEADER) ||
|
||
((PCHANGELOG_BUFFER_HEADER)Where)->Size > BytesLeft ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"I_NetLogonAppendChangeLog: Header size is bogus %ld %ld\n",
|
||
((PCHANGELOG_BUFFER_HEADER)Where)->Size,
|
||
BytesLeft ));
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
goto Cleanup;
|
||
}
|
||
|
||
if ( ((PCHANGELOG_BUFFER_HEADER)Where)->Version != CHANGELOG_BUFFER_VERSION ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"I_NetLogonAppendChangeLog: Header version is bogus %ld\n",
|
||
((PCHANGELOG_BUFFER_HEADER)Where)->Version ));
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
goto Cleanup;
|
||
}
|
||
|
||
if ( ((PCHANGELOG_BUFFER_HEADER)Where)->SequenceNumber != NlGlobalChangeLogSequenceNumber + 1 ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"I_NetLogonAppendChangeLog: Header out of sequence %ld %ld\n",
|
||
((PCHANGELOG_BUFFER_HEADER)Where)->SequenceNumber,
|
||
NlGlobalChangeLogSequenceNumber ));
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
goto Cleanup;
|
||
}
|
||
|
||
NlPrint((NL_CHANGELOG, "I_NetLogonAppendChangeLog: called (%ld)\n", NlGlobalChangeLogSequenceNumber ));
|
||
|
||
NlGlobalChangeLogSequenceNumber += 1;
|
||
BytesLeft -= ((PCHANGELOG_BUFFER_HEADER)Where)->Size;
|
||
Where += ((PCHANGELOG_BUFFER_HEADER)Where)->Size;
|
||
|
||
|
||
//
|
||
// Loop through the individual changes
|
||
//
|
||
|
||
while ( BytesLeft != 0 ) {
|
||
PCHANGELOG_ENTRY ChangeLogEntry;
|
||
ULONG ChangeLogEntrySize;
|
||
|
||
//
|
||
// Ensure that at least the size field is present.
|
||
//
|
||
if ( BytesLeft < sizeof(DWORD) ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"I_NetLogonAppendChangeLog: Bytes left is too small %ld\n", BytesLeft ));
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Ensure the entire change log entry is present.
|
||
//
|
||
|
||
ChangeLogEntrySize = *((PULONG)Where);
|
||
Where += sizeof(ULONG);
|
||
BytesLeft -= sizeof(ULONG);
|
||
|
||
if ( BytesLeft < ChangeLogEntrySize ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"I_NetLogonAppendChangeLog: Bytes left is smaller than entry size %ld %ld\n",
|
||
BytesLeft, ChangeLogEntrySize ));
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
goto Cleanup;
|
||
}
|
||
|
||
ChangeLogEntry = (PCHANGELOG_ENTRY)Where;
|
||
Where += ChangeLogEntrySize;
|
||
BytesLeft -= ChangeLogEntrySize;
|
||
|
||
|
||
//
|
||
// Check the structural integrity of the entry.
|
||
//
|
||
|
||
if ( !NlValidateChangeLogEntry( ChangeLogEntry, ChangeLogEntrySize)) {
|
||
NlPrint((NL_CRITICAL,
|
||
"I_NetLogonAppendChangeLog: ChangeLogEntry is bogus\n" ));
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If this is not an entry for the LSA database,
|
||
// copy the change log entry into the temporary change log.
|
||
//
|
||
// The rational here is that this routine is called on this
|
||
// DC when this DC is in the process of becoming a PDC. A
|
||
// new change log is created that is coppied from the one on
|
||
// what is currently the PDC. Parallel to this, the SAM database
|
||
// is replicated using DS from the current PDC to this DC.
|
||
// However, the LSA database isn't replicated in this process.
|
||
// This is OK since after this machine becomes the PDC, it will
|
||
// change the timestamp of the master database creation forcing
|
||
// BDCs to do a full LSA sync from this PDC next time they send a
|
||
// ssync request. However, if we were to write LSA entries into
|
||
// the change log, we could potentially have different serial
|
||
// numbers for what we currently have in the LSA database locally
|
||
// on this machine and in the change log we got from what is
|
||
// currently the PDC. This would potentially produce event log
|
||
// errors next time LSA tries to write into the log file locally.
|
||
// So we choose not to write the LSA entries in the change log at
|
||
// all.
|
||
//
|
||
|
||
if ( ChangeLogEntry->DBIndex != LSA_DB ) {
|
||
Status = NlCopyChangeLogEntry(
|
||
FALSE, // Entry is NOT version 3
|
||
ChangeLogEntry,
|
||
&NlGlobalTempChangeLogDesc );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"I_NetLogonAppendChangeLog: Cannot copy ChangeLogEntry 0x%lx\n",
|
||
Status ));
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
|
||
//
|
||
// Flush the changes to disk.
|
||
//
|
||
|
||
Status = NlFlushChangeLog( &NlGlobalTempChangeLogDesc );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"I_NetLogonAppendChangeLog: Cannot flush changes 0x%lx\n",
|
||
Status ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
Status = STATUS_SUCCESS;
|
||
Cleanup:
|
||
UNLOCK_CHANGELOG();
|
||
|
||
if ( AllocatedChangeBuffer != NULL ) {
|
||
LocalFree( AllocatedChangeBuffer );
|
||
}
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
I_NetLogonCloseChangeLog(
|
||
IN HANDLE ChangeLogHandle,
|
||
IN BOOLEAN Commit
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function closes a new changelog file.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogHandle - A handle identifying the temporary change log.
|
||
|
||
Commit - If true, the specified changes are written to the primary change log.
|
||
If false, the specified change are deleted.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS - The temporary change log has been successfully opened.
|
||
|
||
STATUS_INVALID_DOMAIN_ROLE - DC is neither PDC nor BDC.
|
||
|
||
STATUS_INVALID_HANDLE - ChangeLogHandle is not valid.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
LPBYTE Where;
|
||
ULONG BytesLeft;
|
||
WCHAR ChangeLogFile[PATHLEN+1];
|
||
|
||
NlPrint((NL_CHANGELOG, "I_NetLogonAppendCloseLog: called (%ld)\n", Commit ));
|
||
|
||
//
|
||
// Ensure the role is right. Otherwise, all the globals used below
|
||
// aren't initialized.
|
||
//
|
||
|
||
if ( NlGlobalChangeLogRole != ChangeLogPrimary &&
|
||
NlGlobalChangeLogRole != ChangeLogBackup ) {
|
||
NlPrint((NL_CHANGELOG,
|
||
"I_NetLogonCloseChangeLog: failed 1\n" ));
|
||
return STATUS_INVALID_DOMAIN_ROLE;
|
||
}
|
||
|
||
//
|
||
// Check the handle.
|
||
//
|
||
|
||
LOCK_CHANGELOG();
|
||
if ( HandleToUlong(ChangeLogHandle) != NlGlobalChangeLogHandle ) {
|
||
Status = STATUS_INVALID_HANDLE;
|
||
goto Cleanup;
|
||
}
|
||
|
||
NlGlobalChangeLogHandle ++; // Invalidate this handle
|
||
|
||
//
|
||
// If the changes are to be committed,
|
||
// copy them now.
|
||
//
|
||
|
||
if ( Commit ) {
|
||
|
||
//
|
||
// Close the existing change log
|
||
//
|
||
|
||
NlCloseChangeLogFile( &NlGlobalChangeLogDesc );
|
||
|
||
//
|
||
// Clone the temporary change log.
|
||
//
|
||
|
||
NlGlobalChangeLogDesc = NlGlobalTempChangeLogDesc;
|
||
NlGlobalChangeLogDesc.FileHandle = INVALID_HANDLE_VALUE; // Don't use the temporary file
|
||
NlGlobalTempChangeLogDesc.Buffer = NULL; // Don't have two refs to the same buffer
|
||
NlGlobalChangeLogDesc.TempLog = FALSE; // Log is no longer the temporary log
|
||
|
||
//
|
||
// Write the cache to the file.
|
||
//
|
||
// Ignore errors since the log is successfully in memory
|
||
//
|
||
|
||
(VOID) NlWriteChangeLogBytes( &NlGlobalChangeLogDesc,
|
||
NlGlobalChangeLogDesc.Buffer,
|
||
NlGlobalChangeLogDesc.BufferSize,
|
||
TRUE ); // Flush the bytes to disk
|
||
}
|
||
|
||
|
||
//
|
||
// Delete the temporary log file.
|
||
//
|
||
|
||
NlCloseChangeLogFile( &NlGlobalTempChangeLogDesc );
|
||
|
||
#ifdef notdef // Leave it around for debugging purposes.
|
||
wcscpy( ChangeLogFile, NlGlobalChangeLogFilePrefix );
|
||
wcscat( ChangeLogFile, TEMP_CHANGELOG_FILE_POSTFIX );
|
||
if ( !DeleteFile( ChangeLogFile ) ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"NlVoidChangeLogEntry: cannot delete temp change log %ld.\n",
|
||
GetLastError() ));
|
||
}
|
||
#endif // notdef
|
||
|
||
Status = STATUS_SUCCESS;
|
||
Cleanup:
|
||
UNLOCK_CHANGELOG();
|
||
return Status;
|
||
}
|
||
|
||
|
||
#if NETLOGONDBG
|
||
|
||
VOID
|
||
PrintChangeLogEntry(
|
||
PCHANGELOG_ENTRY ChangeLogEntry
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine print the content of the given changelog entry.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogEntry -- pointer to the change log entry to print
|
||
|
||
Return Value:
|
||
|
||
none.
|
||
|
||
--*/
|
||
{
|
||
LPSTR DeltaName;
|
||
|
||
switch ( ChangeLogEntry->DeltaType ) {
|
||
case AddOrChangeDomain:
|
||
DeltaName = "AddOrChangeDomain";
|
||
break;
|
||
case AddOrChangeGroup:
|
||
DeltaName = "AddOrChangeGroup";
|
||
break;
|
||
case DeleteGroupByName:
|
||
case DeleteGroup:
|
||
DeltaName = "DeleteGroup";
|
||
break;
|
||
case RenameGroup:
|
||
DeltaName = "RenameGroup";
|
||
break;
|
||
case AddOrChangeUser:
|
||
DeltaName = "AddOrChangeUser";
|
||
break;
|
||
case DeleteUserByName:
|
||
case DeleteUser:
|
||
DeltaName = "DeleteUser";
|
||
break;
|
||
case RenameUser:
|
||
DeltaName = "RenameUser";
|
||
break;
|
||
case ChangeGroupMembership:
|
||
DeltaName = "ChangeGroupMembership";
|
||
break;
|
||
case AddOrChangeAlias:
|
||
DeltaName = "AddOrChangeAlias";
|
||
break;
|
||
case DeleteAlias:
|
||
DeltaName = "DeleteAlias";
|
||
break;
|
||
case RenameAlias:
|
||
DeltaName = "RenameAlias";
|
||
break;
|
||
case ChangeAliasMembership:
|
||
DeltaName = "ChangeAliasMembership";
|
||
break;
|
||
case AddOrChangeLsaPolicy:
|
||
DeltaName = "AddOrChangeLsaPolicy";
|
||
break;
|
||
case AddOrChangeLsaTDomain:
|
||
DeltaName = "AddOrChangeLsaTDomain";
|
||
break;
|
||
case DeleteLsaTDomain:
|
||
DeltaName = "DeleteLsaTDomain";
|
||
break;
|
||
case AddOrChangeLsaAccount:
|
||
DeltaName = "AddOrChangeLsaAccount";
|
||
break;
|
||
case DeleteLsaAccount:
|
||
DeltaName = "DeleteLsaAccount";
|
||
break;
|
||
case AddOrChangeLsaSecret:
|
||
DeltaName = "AddOrChangeLsaSecret";
|
||
break;
|
||
case DeleteLsaSecret:
|
||
DeltaName = "DeleteLsaSecret";
|
||
break;
|
||
case SerialNumberSkip:
|
||
DeltaName = "SerialNumberSkip";
|
||
break;
|
||
case DummyChangeLogEntry:
|
||
DeltaName = "DummyChangeLogEntry";
|
||
break;
|
||
|
||
default:
|
||
DeltaName ="(Unknown)";
|
||
break;
|
||
}
|
||
|
||
NlPrint((NL_CHANGELOG,
|
||
"DeltaType %s (%ld) SerialNumber: %lx %lx",
|
||
DeltaName,
|
||
ChangeLogEntry->DeltaType,
|
||
ChangeLogEntry->SerialNumber.HighPart,
|
||
ChangeLogEntry->SerialNumber.LowPart ));
|
||
|
||
if ( ChangeLogEntry->ObjectRid != 0 ) {
|
||
NlPrint((NL_CHANGELOG," Rid: 0x%lx", ChangeLogEntry->ObjectRid ));
|
||
}
|
||
if ( ChangeLogEntry->Flags & CHANGELOG_PDC_PROMOTION ) {
|
||
NlPrint((NL_CHANGELOG," Promotion" ));
|
||
}
|
||
|
||
if( ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED ) {
|
||
NlPrint(( NL_CHANGELOG, " Name: '" FORMAT_LPWSTR "'",
|
||
(LPWSTR)((PBYTE)(ChangeLogEntry)+ sizeof(CHANGELOG_ENTRY))));
|
||
}
|
||
|
||
if( ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED ) {
|
||
NlPrint((NL_CHANGELOG," Sid: "));
|
||
NlpDumpSid( NL_CHANGELOG,
|
||
(PSID)((PBYTE)(ChangeLogEntry)+ sizeof(CHANGELOG_ENTRY)) );
|
||
} else {
|
||
NlPrint((NL_CHANGELOG,"\n" ));
|
||
}
|
||
}
|
||
#endif // NETLOGONDBG
|
||
|
||
|
||
|
||
NTSTATUS
|
||
NlWriteChangeLogEntry(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN PCHANGELOG_ENTRY ChangeLogEntry,
|
||
IN PSID ObjectSid,
|
||
IN PUNICODE_STRING ObjectName,
|
||
IN BOOLEAN FlushIt
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is the actual worker for the I_NetNotifyDelta(). This function
|
||
acquires the sufficient size memory block from the change log
|
||
buffer, writes the fixed and variable portions of the change log
|
||
delta in change log buffer and also writes the delta into change log
|
||
file.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer being used
|
||
|
||
ChangeLogEntry - pointer to the fixed portion of the change log.
|
||
|
||
ObjectSid - pointer to the variable field SID.
|
||
|
||
ObjectName - pointer to the variable field Name.
|
||
|
||
FlushIt - True if the written bytes are to be flushed to disk
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS - The Service completed successfully.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS Status;
|
||
DWORD LogSize;
|
||
PCHANGELOG_BLOCK_HEADER LogBlock;
|
||
PCHANGELOG_BLOCK_HEADER FreeBlock;
|
||
LPBYTE AllocatedChangeLogEntry;
|
||
|
||
//
|
||
// Make sure that the change log cache is available.
|
||
//
|
||
|
||
if ( ChangeLogDesc->Buffer == NULL ) {
|
||
return STATUS_INTERNAL_ERROR;
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Determine the size of this change log entry.
|
||
//
|
||
|
||
LogSize = sizeof(CHANGELOG_ENTRY);
|
||
|
||
//
|
||
// Ensure we've got the right data for those deltas we care about
|
||
//
|
||
|
||
switch (ChangeLogEntry->DeltaType) {
|
||
case AddOrChangeLsaTDomain:
|
||
case DeleteLsaTDomain:
|
||
case AddOrChangeLsaAccount:
|
||
case DeleteLsaAccount:
|
||
NlAssert( ObjectSid != NULL );
|
||
if( ObjectSid != NULL ) {
|
||
ChangeLogEntry->Flags |= CHANGELOG_SID_SPECIFIED;
|
||
LogSize += RtlLengthSid( ObjectSid );
|
||
}
|
||
break;
|
||
|
||
case AddOrChangeLsaSecret:
|
||
case DeleteLsaSecret:
|
||
case DeleteGroup:
|
||
case DeleteUser:
|
||
|
||
// NlAssert( ObjectName != NULL && ObjectName->Buffer != NULL && ObjectName->Length != 0 );
|
||
if( ObjectName != NULL && ObjectName->Buffer != NULL && ObjectName->Length != 0 ) {
|
||
ChangeLogEntry->Flags |= CHANGELOG_NAME_SPECIFIED;
|
||
LogSize += ObjectName->Length + sizeof(WCHAR);
|
||
}
|
||
break;
|
||
|
||
//
|
||
// For all other delta types, save the data if it's there.
|
||
//
|
||
default:
|
||
|
||
if( ObjectName != NULL && ObjectName->Buffer != NULL && ObjectName->Length != 0 ) {
|
||
ChangeLogEntry->Flags |= CHANGELOG_NAME_SPECIFIED;
|
||
LogSize += ObjectName->Length + sizeof(WCHAR);
|
||
} else if( ObjectSid != NULL ) {
|
||
ChangeLogEntry->Flags |= CHANGELOG_SID_SPECIFIED;
|
||
LogSize += RtlLengthSid( ObjectSid );
|
||
}
|
||
break;
|
||
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Serialize access to the change log
|
||
//
|
||
|
||
LOCK_CHANGELOG();
|
||
|
||
//
|
||
// Validate the serial number order of this new entry
|
||
//
|
||
// If we're out of sync with the caller,
|
||
// clear the change log and start all over again.
|
||
//
|
||
// The global serial number array entry for this database must either
|
||
// be zero (indicating no entries for this database) or one less than
|
||
// the new serial number being added.
|
||
//
|
||
|
||
if ( ChangeLogDesc->SerialNumber[ChangeLogEntry->DBIndex].QuadPart != 0 ) {
|
||
LARGE_INTEGER ExpectedSerialNumber;
|
||
LARGE_INTEGER OldSerialNumber;
|
||
|
||
ExpectedSerialNumber.QuadPart =
|
||
ChangeLogDesc->SerialNumber[ChangeLogEntry->DBIndex].QuadPart + 1;
|
||
|
||
//
|
||
// If the serial number jumped by the promotion increment,
|
||
// set the flag in the change log entry indicating this is
|
||
// a promotion to PDC.
|
||
//
|
||
|
||
if ( ChangeLogEntry->SerialNumber.QuadPart ==
|
||
ExpectedSerialNumber.QuadPart +
|
||
NlGlobalChangeLogPromotionIncrement.QuadPart ) {
|
||
|
||
ChangeLogEntry->Flags |= CHANGELOG_PDC_PROMOTION;
|
||
}
|
||
|
||
if ( !IsSerialNumberEqual( ChangeLogDesc,
|
||
ChangeLogEntry,
|
||
&ExpectedSerialNumber )) {
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"NlWriteChangeLogEntry: Serial numbers not contigous %lx %lx and %lx %lx\n",
|
||
ChangeLogEntry->SerialNumber.HighPart,
|
||
ChangeLogEntry->SerialNumber.LowPart,
|
||
ExpectedSerialNumber.HighPart,
|
||
ExpectedSerialNumber.LowPart ));
|
||
|
||
//
|
||
// write event log.
|
||
//
|
||
|
||
NlpWriteEventlog (
|
||
NELOG_NetlogonChangeLogCorrupt,
|
||
EVENTLOG_ERROR_TYPE,
|
||
(LPBYTE)&(ChangeLogEntry->DBIndex),
|
||
sizeof(ChangeLogEntry->DBIndex),
|
||
NULL,
|
||
0 );
|
||
|
||
|
||
//
|
||
// If the change log is merely newer than the SAM database,
|
||
// we truncate entries newer than what exists in SAM.
|
||
//
|
||
|
||
OldSerialNumber.QuadPart = ChangeLogEntry->SerialNumber.QuadPart - 1;
|
||
|
||
(VOID) NlFixChangeLog( ChangeLogDesc, ChangeLogEntry->DBIndex, OldSerialNumber );
|
||
}
|
||
|
||
//
|
||
// If this is the first entry written to the change log for this database,
|
||
// mark it as a promotion.
|
||
//
|
||
|
||
} else {
|
||
//
|
||
// Only mark entries that might possibly be a promotion.
|
||
//
|
||
switch (ChangeLogEntry->DeltaType) {
|
||
case AddOrChangeDomain:
|
||
case AddOrChangeLsaPolicy:
|
||
ChangeLogEntry->Flags |= CHANGELOG_PDC_PROMOTION;
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// Validate the list before changing anything
|
||
//
|
||
|
||
#if DBG
|
||
NlAssert( ValidateList( ChangeLogDesc, FALSE) );
|
||
#endif // DBG
|
||
|
||
|
||
//
|
||
// copy fixed portion
|
||
//
|
||
|
||
Status = NlAllocChangeLogBlock( ChangeLogDesc, LogSize, &LogBlock );
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
goto Cleanup;
|
||
}
|
||
AllocatedChangeLogEntry = ((LPBYTE)LogBlock) + sizeof(CHANGELOG_BLOCK_HEADER);
|
||
RtlCopyMemory( AllocatedChangeLogEntry, ChangeLogEntry, sizeof(CHANGELOG_ENTRY) );
|
||
|
||
|
||
//
|
||
// copy variable fields
|
||
//
|
||
|
||
if( ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED ) {
|
||
|
||
RtlCopyMemory( AllocatedChangeLogEntry + sizeof(CHANGELOG_ENTRY),
|
||
ObjectSid,
|
||
RtlLengthSid( ObjectSid ) );
|
||
} else if( ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED ) {
|
||
|
||
RtlCopyMemory( AllocatedChangeLogEntry + sizeof(CHANGELOG_ENTRY),
|
||
ObjectName->Buffer,
|
||
ObjectName->Length );
|
||
|
||
//
|
||
// terminate unicode string
|
||
//
|
||
|
||
*(WCHAR *)(AllocatedChangeLogEntry + sizeof(CHANGELOG_ENTRY) +
|
||
ObjectName->Length) = 0;
|
||
}
|
||
|
||
//
|
||
// Be verbose
|
||
//
|
||
|
||
#if NETLOGONDBG
|
||
PrintChangeLogEntry( (PCHANGELOG_ENTRY)AllocatedChangeLogEntry );
|
||
#endif // NETLOGONDBG
|
||
|
||
|
||
|
||
//
|
||
// Write the cache entry to the file.
|
||
//
|
||
// Actually, write this entry plus the header and trailer of the free
|
||
// block that follows. If the free block is huge, write the free
|
||
// block trailer separately.
|
||
//
|
||
|
||
FreeBlock =
|
||
(PCHANGELOG_BLOCK_HEADER)((LPBYTE)LogBlock + LogBlock->BlockSize);
|
||
|
||
if ( FreeBlock->BlockSize >= 4096 ) {
|
||
|
||
Status = NlWriteChangeLogBytes(
|
||
ChangeLogDesc,
|
||
(LPBYTE)LogBlock,
|
||
LogBlock->BlockSize + sizeof(CHANGELOG_BLOCK_HEADER),
|
||
FlushIt );
|
||
|
||
if ( NT_SUCCESS(Status) ) {
|
||
Status = NlWriteChangeLogBytes(
|
||
ChangeLogDesc,
|
||
(LPBYTE)ChangeLogBlockTrailer(FreeBlock),
|
||
sizeof(CHANGELOG_BLOCK_TRAILER),
|
||
FlushIt );
|
||
}
|
||
|
||
} else {
|
||
|
||
Status = NlWriteChangeLogBytes(
|
||
ChangeLogDesc,
|
||
(LPBYTE)LogBlock,
|
||
LogBlock->BlockSize + FreeBlock->BlockSize,
|
||
FlushIt );
|
||
}
|
||
|
||
|
||
//
|
||
// Done.
|
||
//
|
||
|
||
ChangeLogDesc->SerialNumber[ChangeLogEntry->DBIndex] = ChangeLogEntry->SerialNumber;
|
||
ChangeLogDesc->EntryCount[ChangeLogEntry->DBIndex] ++;
|
||
|
||
//
|
||
// Validate the list before returning from here.
|
||
//
|
||
Cleanup:
|
||
|
||
#if DBG
|
||
NlAssert( ValidateList( ChangeLogDesc, FALSE) );
|
||
#endif // DBG
|
||
|
||
|
||
UNLOCK_CHANGELOG();
|
||
return Status;
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
NlOpenChangeLogFile(
|
||
IN LPWSTR ChangeLogFileName,
|
||
OUT PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN BOOLEAN ReadOnly
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Open the change log file (netlogon.chg) for reading or writing one or
|
||
more records. Create this file if it does not exist or is out of
|
||
sync with the SAM database (see note below).
|
||
|
||
This file must be opened for R/W (deny-none share mode) at the time
|
||
the cache is initialized. If the file already exists when NETLOGON
|
||
service started, its contents will be cached in its entirety
|
||
provided the last change log record bears the same serial number as
|
||
the serial number field in SAM database else this file will be
|
||
removed and a new one created. If the change log file did not exist
|
||
then it will be created.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogFileName - Name of the changelog file to open.
|
||
|
||
ChangeLogDesc -- On success, returns a description of the Changelog buffer
|
||
being used
|
||
|
||
ReadOnly -- True if the file should be openned read only.
|
||
|
||
Return Value:
|
||
|
||
NT Status code
|
||
|
||
--*/
|
||
{
|
||
|
||
DWORD WinError;
|
||
DWORD BytesRead;
|
||
DWORD MinChangeLogSize;
|
||
|
||
//
|
||
// Open change log file if exists
|
||
//
|
||
|
||
ChangeLogDesc->FileHandle = CreateFileW(
|
||
ChangeLogFileName,
|
||
ReadOnly ? GENERIC_READ : (GENERIC_READ | GENERIC_WRITE),
|
||
ReadOnly ? (FILE_SHARE_READ | FILE_SHARE_WRITE) : FILE_SHARE_READ, // allow backups and debugging
|
||
NULL, // Supply better security ??
|
||
OPEN_EXISTING, // Only open it if it exists
|
||
FILE_ATTRIBUTE_NORMAL,
|
||
NULL ); // No template
|
||
|
||
if ( ChangeLogDesc->FileHandle == INVALID_HANDLE_VALUE) {
|
||
WinError = GetLastError();
|
||
|
||
NlPrint(( NL_CRITICAL,
|
||
FORMAT_LPWSTR ": Unable to open. %ld\n",
|
||
ChangeLogFileName,
|
||
WinError ));
|
||
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Get the size of the file.
|
||
//
|
||
|
||
ChangeLogDesc->BufferSize = GetFileSize( ChangeLogDesc->FileHandle, NULL );
|
||
|
||
if ( ChangeLogDesc->BufferSize == 0xFFFFFFFF ) {
|
||
|
||
WinError = GetLastError();
|
||
NlPrint((NL_CRITICAL,
|
||
"%ws: Unable to GetFileSize: %ld \n",
|
||
ChangeLogFileName,
|
||
WinError));
|
||
goto Cleanup;
|
||
}
|
||
|
||
// ?? consider aligning to ALIGN_WORST
|
||
MinChangeLogSize = MIN_CHANGELOGSIZE;
|
||
|
||
if ( ChangeLogDesc->BufferSize < MinChangeLogSize ||
|
||
ChangeLogDesc->BufferSize > MAX_CHANGELOGSIZE ) {
|
||
|
||
WinError = ERROR_INTERNAL_DB_CORRUPTION;
|
||
|
||
NlPrint((NL_CRITICAL, FORMAT_LPWSTR ": Changelog size is invalid. %ld.\n",
|
||
ChangeLogFileName,
|
||
ChangeLogDesc->BufferSize ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Allocate and initialize the change log cache.
|
||
//
|
||
|
||
ChangeLogDesc->Buffer = NetpMemoryAllocate( ChangeLogDesc->BufferSize );
|
||
if (ChangeLogDesc->Buffer == NULL) {
|
||
NlPrint((NL_CRITICAL, FORMAT_LPWSTR ": Cannot allocate Changelog buffer. %ld.\n",
|
||
ChangeLogFileName,
|
||
ChangeLogDesc->BufferSize ));
|
||
WinError = ERROR_NOT_ENOUGH_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
RtlZeroMemory(ChangeLogDesc->Buffer, ChangeLogDesc->BufferSize);
|
||
|
||
|
||
//
|
||
// Check the signature at the front of the change log.
|
||
//
|
||
// It won't be there if we just created the file.
|
||
//
|
||
|
||
if ( !ReadFile( ChangeLogDesc->FileHandle,
|
||
ChangeLogDesc->Buffer,
|
||
ChangeLogDesc->BufferSize,
|
||
&BytesRead,
|
||
NULL ) ) { // Not Overlapped
|
||
|
||
WinError = GetLastError();
|
||
|
||
NlPrint(( NL_CRITICAL,
|
||
FORMAT_LPWSTR ": Unable to read from changelog file. %ld\n",
|
||
ChangeLogFileName,
|
||
WinError ));
|
||
|
||
goto Cleanup;
|
||
}
|
||
|
||
if ( BytesRead != ChangeLogDesc->BufferSize ) {
|
||
|
||
WinError = ERROR_INTERNAL_DB_CORRUPTION;
|
||
|
||
NlPrint(( NL_CRITICAL,
|
||
FORMAT_LPWSTR ": Couldn't read entire file. %ld\n",
|
||
ChangeLogFileName,
|
||
WinError ));
|
||
|
||
|
||
goto Cleanup;
|
||
}
|
||
|
||
if ( strncmp((PCHAR)ChangeLogDesc->Buffer,
|
||
CHANGELOG_SIG, sizeof(CHANGELOG_SIG)) == 0) {
|
||
ChangeLogDesc->Version3 = FALSE;
|
||
|
||
} else if ( strncmp((PCHAR)ChangeLogDesc->Buffer,
|
||
CHANGELOG_SIG_V3, sizeof(CHANGELOG_SIG_V3)) == 0) {
|
||
ChangeLogDesc->Version3 = TRUE;
|
||
} else {
|
||
WinError = ERROR_INTERNAL_ERROR;
|
||
|
||
NlPrint(( NL_CRITICAL,
|
||
FORMAT_LPWSTR ": Invalid signature. %ld\n",
|
||
ChangeLogFileName,
|
||
WinError ));
|
||
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Find the Head and Tail pointers of the circular log.
|
||
//
|
||
|
||
if( !InitChangeLogHeadAndTail( ChangeLogDesc, FALSE ) ) {
|
||
WinError = ERROR_INTERNAL_DB_CORRUPTION;
|
||
|
||
NlPrint(( NL_CRITICAL,
|
||
FORMAT_LPWSTR ": couldn't find head/tail. %ld\n",
|
||
ChangeLogFileName,
|
||
WinError ));
|
||
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
|
||
WinError = NO_ERROR;
|
||
|
||
//
|
||
// Free any resources on error.
|
||
//
|
||
Cleanup:
|
||
|
||
if ( WinError != NO_ERROR ) {
|
||
NlCloseChangeLogFile( ChangeLogDesc );
|
||
} else {
|
||
NlPrint((NL_CHANGELOG, "%ws: Changelog successfully opened.\n",
|
||
ChangeLogFileName ));
|
||
}
|
||
|
||
return NetpApiStatusToNtStatus(WinError);
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
VOID
|
||
NlCloseChangeLogFile(
|
||
IN PCHANGELOG_DESCRIPTOR ChangeLogDesc
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function closes the change log file and frees up the resources
|
||
consumed by the change log desriptor.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- Description of the Changelog buffer being used
|
||
|
||
Return Value:
|
||
|
||
NT Status code
|
||
|
||
--*/
|
||
{
|
||
|
||
LOCK_CHANGELOG();
|
||
|
||
NlPrint((NL_CHANGELOG, "%s log closed.\n",
|
||
ChangeLogDesc->TempLog ? "TempChange" : "Change" ));
|
||
|
||
//
|
||
// free up the change log cache.
|
||
//
|
||
|
||
if ( ChangeLogDesc->Buffer != NULL ) {
|
||
NetpMemoryFree( ChangeLogDesc->Buffer );
|
||
ChangeLogDesc->Buffer = NULL;
|
||
}
|
||
|
||
ChangeLogDesc->Head = NULL;
|
||
ChangeLogDesc->Tail = NULL;
|
||
|
||
ChangeLogDesc->FirstBlock = NULL;
|
||
ChangeLogDesc->BufferEnd = NULL;
|
||
|
||
ChangeLogDesc->LastDirtyByte = 0;
|
||
ChangeLogDesc->FirstDirtyByte = 0;
|
||
|
||
//
|
||
// Close the change log file
|
||
//
|
||
|
||
if ( ChangeLogDesc->FileHandle != INVALID_HANDLE_VALUE ) {
|
||
CloseHandle( ChangeLogDesc->FileHandle );
|
||
ChangeLogDesc->FileHandle = INVALID_HANDLE_VALUE;
|
||
}
|
||
|
||
UNLOCK_CHANGELOG();
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
NlResizeChangeLogFile(
|
||
IN OUT PCHANGELOG_DESCRIPTOR ChangeLogDesc,
|
||
IN DWORD NewChangeLogSize
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
The buffer described by ChageLogDesc is converted to
|
||
the size requested by NewChangeLogSize and is converted from any
|
||
old format change log to the latest format.
|
||
|
||
NOTE: This function must be called with the change log locked.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogDesc -- a description of the Changelog buffer.
|
||
|
||
NewChangeLogSize -- Size (in bytes) of the new change log.
|
||
|
||
Return Value:
|
||
|
||
NT Status code
|
||
|
||
On error, the ChangeLogDesc will still be intact. Merely the size
|
||
changes will not have happened
|
||
|
||
--*/
|
||
{
|
||
CHANGELOG_DESCRIPTOR OutChangeLogDesc;
|
||
NTSTATUS Status;
|
||
|
||
//
|
||
// If the current buffer is perfect,
|
||
// just use it.
|
||
//
|
||
|
||
if ( !ChangeLogDesc->Version3 &&
|
||
ChangeLogDesc->BufferSize == NewChangeLogSize ) {
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
//
|
||
// Initialize the template change log descriptor
|
||
//
|
||
|
||
InitChangeLogDesc( &OutChangeLogDesc );
|
||
|
||
//
|
||
// Close the file so we can resize it.
|
||
//
|
||
|
||
if ( ChangeLogDesc->FileHandle != INVALID_HANDLE_VALUE ) {
|
||
CloseHandle( ChangeLogDesc->FileHandle );
|
||
ChangeLogDesc->FileHandle = INVALID_HANDLE_VALUE;
|
||
}
|
||
|
||
//
|
||
// Start with a newly initialized change log,
|
||
//
|
||
|
||
Status = NlResetChangeLog( &OutChangeLogDesc, NewChangeLogSize );
|
||
|
||
if ( !NT_SUCCESS(Status)) {
|
||
return Status;
|
||
}
|
||
|
||
//
|
||
// We're done if the old change log is empty.
|
||
//
|
||
|
||
if ( !ChangeLogIsEmpty(ChangeLogDesc) ) {
|
||
|
||
//
|
||
// Loop through the old change log copying it to the new changelog,
|
||
//
|
||
|
||
PCHANGELOG_ENTRY SourceChangeLogEntry = (PCHANGELOG_ENTRY)
|
||
(ChangeLogDesc->Head + 1);
|
||
|
||
do {
|
||
Status = NlCopyChangeLogEntry( ChangeLogDesc->Version3,
|
||
SourceChangeLogEntry,
|
||
&OutChangeLogDesc );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlCloseChangeLogFile( &OutChangeLogDesc );
|
||
return Status;
|
||
}
|
||
|
||
} while ( (SourceChangeLogEntry =
|
||
NlMoveToNextChangeLogEntry( ChangeLogDesc, SourceChangeLogEntry )) != NULL );
|
||
|
||
//
|
||
// Flsuh all the changes to the change log file now.
|
||
//
|
||
|
||
Status = NlFlushChangeLog( &OutChangeLogDesc );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlCloseChangeLogFile( &OutChangeLogDesc );
|
||
return Status;
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Free the old change log buffer.
|
||
//
|
||
|
||
NlCloseChangeLogFile( ChangeLogDesc );
|
||
|
||
//
|
||
// Copy the new descriptor over the old descriptor
|
||
//
|
||
|
||
*ChangeLogDesc = OutChangeLogDesc;
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
#if NETLOGONDBG
|
||
|
||
DWORD
|
||
NlBackupChangeLogFile(
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Backup change log content. Since the cache and the change log file
|
||
content are identical, write cache content to the backup file.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS - The Service completed successfully.
|
||
|
||
--*/
|
||
{
|
||
HANDLE BackupChangeLogHandle;
|
||
|
||
WCHAR BackupChangelogFile[MAX_PATH+1];
|
||
DWORD WinError;
|
||
|
||
if( NlGlobalChangeLogFilePrefix[0] == L'\0' ) {
|
||
|
||
return ERROR_FILE_NOT_FOUND;
|
||
}
|
||
|
||
//
|
||
// make backup file name.
|
||
//
|
||
|
||
wcscpy( BackupChangelogFile, NlGlobalChangeLogFilePrefix );
|
||
wcscat( BackupChangelogFile, BACKUP_CHANGELOG_FILE_POSTFIX );
|
||
|
||
|
||
|
||
//
|
||
// Create change log file. If it exists already then truncate it.
|
||
//
|
||
// Note : if a valid change log file exists on the system, then we
|
||
// would have opened at initialization time.
|
||
//
|
||
|
||
BackupChangeLogHandle = CreateFileW(
|
||
BackupChangelogFile,
|
||
GENERIC_READ | GENERIC_WRITE,
|
||
FILE_SHARE_READ, // allow backups and debugging
|
||
NULL, // Supply better security ??
|
||
CREATE_ALWAYS, // Overwrites always
|
||
FILE_ATTRIBUTE_NORMAL,
|
||
NULL ); // No template
|
||
|
||
if (BackupChangeLogHandle == INVALID_HANDLE_VALUE) {
|
||
|
||
|
||
NlPrint((NL_CRITICAL,"Unable to create backup changelog file "
|
||
"WinError = %ld \n", WinError = GetLastError() ));
|
||
|
||
return WinError;
|
||
}
|
||
|
||
//
|
||
// Write cache in changelog file if the cache is valid.
|
||
//
|
||
|
||
if( NlGlobalChangeLogDesc.Buffer != NULL ) {
|
||
|
||
OVERLAPPED Overlapped;
|
||
DWORD BytesWritten;
|
||
|
||
//
|
||
// Seek to appropriate offset in the file.
|
||
//
|
||
|
||
RtlZeroMemory( &Overlapped, sizeof(Overlapped) );
|
||
|
||
LOCK_CHANGELOG();
|
||
|
||
if ( !WriteFile( BackupChangeLogHandle,
|
||
NlGlobalChangeLogDesc.Buffer,
|
||
NlGlobalChangeLogDesc.BufferSize,
|
||
&BytesWritten,
|
||
&Overlapped ) ) {
|
||
|
||
UNLOCK_CHANGELOG();
|
||
NlPrint((NL_CRITICAL, "Write to Backup ChangeLog failed %ld\n",
|
||
WinError = GetLastError() ));
|
||
|
||
goto Cleanup;
|
||
}
|
||
|
||
UNLOCK_CHANGELOG();
|
||
|
||
//
|
||
// Ensure all the bytes made it.
|
||
//
|
||
|
||
if ( BytesWritten != NlGlobalChangeLogDesc.BufferSize ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"Write to Backup ChangeLog bad byte count %ld s.b. %ld\n",
|
||
BytesWritten,
|
||
NlGlobalChangeLogDesc.BufferSize ));
|
||
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
Cleanup:
|
||
|
||
CloseHandle( BackupChangeLogHandle );
|
||
return ERROR_SUCCESS;
|
||
}
|
||
|
||
#endif // NETLOGONDBG
|
||
|