You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4821 lines
128 KiB
4821 lines
128 KiB
/*++
|
|
|
|
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[MAX_PATH+CHANGELOG_FILE_POSTFIX_LENGTH+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;
|
|
|
|
}
|
|
|
|
VOID
|
|
NlWriteChangeLogCorruptEvent(
|
|
IN NTSTATUS Status,
|
|
IN DWORD DbIndex
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine writes the event log message stating that
|
|
the change log file is corrupted.
|
|
|
|
Arguments:
|
|
|
|
Status -- Status code of the failure.
|
|
|
|
DBIndex -- Index of the database that is corrupted.
|
|
If not void DB, the database name will be logged.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
LPWSTR Database;
|
|
LPWSTR MsgStrings[1];
|
|
|
|
#ifdef _NETLOGON_SERVER
|
|
|
|
//
|
|
// If caller is calling when the netlogon service isn't running,
|
|
// return. Otherwise, we may AV when accessing SAM handle
|
|
// in the primary DomainInfo struct that may not be initialized.
|
|
//
|
|
|
|
if ( !NlStartNetlogonCall() ) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Avoid the event if we are not in mixed mode
|
|
// when the change log isn't used
|
|
//
|
|
|
|
if ( SamIMixedDomain(NlGlobalDomainInfo->DomSamServerHandle) ) {
|
|
|
|
if ( DbIndex == LSA_DB ) {
|
|
Database = L"LSA";
|
|
} else if ( DbIndex == SAM_DB ) {
|
|
Database = L"SAM";
|
|
} else if ( DbIndex == BUILTIN_DB ) {
|
|
Database = L"BUILTIN";
|
|
} else {
|
|
Database = L"\0"; // void DB
|
|
}
|
|
MsgStrings[0] = Database;
|
|
|
|
//
|
|
// Be consistent with the old versions of the log.
|
|
// If the database is not void, the DB index should be
|
|
// written into the raw data. Otherwise, the Status
|
|
// should be written into the raw data.
|
|
//
|
|
NlpWriteEventlog ( NELOG_NetlogonChangeLogCorrupt,
|
|
EVENTLOG_WARNING_TYPE,
|
|
(DbIndex != VOID_DB) ?
|
|
(LPBYTE)&DbIndex :
|
|
(LPBYTE)&Status,
|
|
(DbIndex != VOID_DB) ?
|
|
sizeof(DbIndex) :
|
|
sizeof(Status),
|
|
MsgStrings,
|
|
1 );
|
|
}
|
|
|
|
//
|
|
// Indicate that we are done using the domain info
|
|
//
|
|
|
|
NlEndNetlogonCall();
|
|
|
|
#else
|
|
|
|
UNREFERENCED_PARAMETER( Status );
|
|
UNREFERENCED_PARAMETER( DbIndex );
|
|
|
|
#endif // _NETLOGON_SERVER
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
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.
|
|
//
|
|
NlWriteChangeLogCorruptEvent( Status,
|
|
VOID_DB ); // no particular DB
|
|
}
|
|
|
|
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 = NULL;
|
|
|
|
//
|
|
// 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 = NULL;
|
|
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
|
|
//
|
|
|
|
NlWriteChangeLogCorruptEvent( STATUS_INTERNAL_DB_CORRUPTION,
|
|
DBIndex );
|
|
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;
|
|
|
|
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.
|
|
{
|
|
WCHAR ChangeLogFile[MAX_PATH+CHANGELOG_FILE_POSTFIX_LENGTH+1];
|
|
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.
|
|
//
|
|
|
|
NlWriteChangeLogCorruptEvent( STATUS_INTERNAL_DB_CORRUPTION,
|
|
ChangeLogEntry->DBIndex );
|
|
|
|
//
|
|
// 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 ) {
|
|
|
|
if ( ObjectName != NULL ) {
|
|
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+CHANGELOG_FILE_POSTFIX_LENGTH+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
|
|
|