Leaked source code of windows server 2003
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.
 
 
 
 
 
 

1113 lines
29 KiB

/*++
Copyright (c) 1992 Microsoft Corporation
Module Name:
Regnckey.c
Abstract:
This module contains the client side wrappers for the Win32 Registry
APIs to notify a caller about a changed Key value. That is:
- RegNotifyChangeKey
Author:
David J. Gilman (davegi) 10-Feb-1992
Notes:
The implementation of RegNotifyChangeKeyValue involves >= 4 threads: 2 on
the client side and >= 2 on the server side.
Client:
Thread 1.- The user's thread executing the RegNotifyChangeKeyValue.
This threads does:
- If thread #2 has not been created yet, it creates a
named pipe and thread #2.
- Does a synchronous RPC to the server
Thread 2.- This thread reads events from the named pipe and signals
them. The writers to the pipe are the RPC servers which
thread 1 has called.
Server:
Thread 1.- This thread services the RPC from the client side. It
calls the NT notification API and adds the notification
handle to a "notification list".
Thread 2.- This thread waits on part of the "notification list",
telling the original client (via named pipe) what events
need to be signaled.
Threads 3... etc. Same as thread 2.
Revision History:
02-Apr-1992 Ramon J. San Andres (ramonsa)
Changed to use RPC.
--*/
#include <rpc.h>
#include "regrpc.h"
#include "client.h"
#include <stdlib.h>
NTSTATUS BaseRegNotifyClassKey(
IN HKEY hKey,
IN HANDLE hEvent,
IN PIO_STATUS_BLOCK pLocalIoStatusBlock,
IN DWORD dwNotifyFilter,
IN BOOLEAN fWatchSubtree,
IN BOOLEAN fAsynchronous);
//
// Used by local call to NtNotifyChangeKey.
//
IO_STATUS_BLOCK LocalIoStatusBlock;
#ifndef REMOTE_NOTIFICATION_DISABLED
//
// Named pipe full paths.
//
#define NAMED_PIPE_HERE L"\\Device\\NamedPipe\\"
//
// Maximum number of times we will retry to create a pipe if there are
// name conflicts.
//
#define MAX_PIPE_RETRIES 1000
//
// Local variables.
//
//
// Critical section to control access to notification structures
//
RTL_CRITICAL_SECTION NotificationCriticalSection;
//
// Our machine name
//
UNICODE_STRING OurMachineName;
WCHAR OurMachineNameBuffer[ MAX_PATH ];
//
// Named pipe used for notification
//
UNICODE_STRING NotificationPipeName;
WCHAR NotificationPipeNameBuffer[ MAX_PATH ];
HANDLE NotificationPipeHandle;
RPC_SECURITY_ATTRIBUTES NotificationPipeSaRpc;
//
// Security descriptor used in the named pipe
//
SECURITY_DESCRIPTOR SecurityDescriptor;
PACL Acl;
BOOL SecurityDescriptorInitialized;
//
// Notification thread
//
HANDLE NotificationThread;
DWORD NotificationClientId;
//
// Local prototypes
//
LONG
CreateNotificationPipe(
);
VOID
NotificationHandler(
);
#endif // REMOTE_NOTIFICATION_DISABLED
#ifndef REMOTE_NOTIFICATION_DISABLED
LONG
InitializeNotificationPipeSecurityDescriptor(
)
/*++
Routine Description:
Initialize the security descriptor (global variable) to be attached to
the named pipe.
Arguments:
None
Return Value:
LONG - Returns a win32 error code.
--*/
{
SID_IDENTIFIER_AUTHORITY WorldSidAuthority = SECURITY_WORLD_SID_AUTHORITY;
ULONG AclLength;
PSID WorldSid;
NTSTATUS NtStatus;
//
// Initialize global variables
//
SecurityDescriptorInitialized = FALSE;
Acl = NULL;
//
// Get World SID
//
NtStatus = RtlAllocateAndInitializeSid( &WorldSidAuthority,
1,
SECURITY_WORLD_RID,
0, 0, 0, 0, 0, 0, 0,
&WorldSid
);
ASSERT( NT_SUCCESS( NtStatus ) );
if ( !NT_SUCCESS( NtStatus )) {
#if DBG
DbgPrint( "WINREG: Unable to allocate and initialize SID, NtStatus = %x \n", NtStatus );
#endif
return( RtlNtStatusToDosError( NtStatus ) );
}
//
// Allocate buffer for ACL.
// This buffer should be big enough for the ACL header and for each ACE.
// Each ACE needs an ACE header.
//
AclLength = sizeof( ACL ) +
sizeof( ACCESS_ALLOWED_ACE ) +
GetLengthSid( WorldSid ) +
sizeof( DWORD );
Acl = RtlAllocateHeap( RtlProcessHeap(), 0, AclLength );
ASSERT( Acl != NULL );
if( Acl == NULL ) {
#if DBG
DbgPrint( "WINREG: Unable to allocate memory, NtStatus = %x \n", NtStatus );
#endif
RtlFreeSid( WorldSid );
return( ERROR_OUTOFMEMORY );
}
//
// Build ACL: World has all access
//
NtStatus = RtlCreateAcl( (PACL)Acl,
AclLength,
ACL_REVISION2
);
ASSERT( NT_SUCCESS( NtStatus ) );
if ( !NT_SUCCESS( NtStatus )) {
#if DBG
DbgPrint( "WINREG: Unable to create ACL, NtStatus = %x \n", NtStatus );
#endif
RtlFreeSid( WorldSid );
RtlFreeHeap( RtlProcessHeap(), 0, Acl );
return( RtlNtStatusToDosError( NtStatus ) );
}
NtStatus = RtlAddAccessAllowedAce( (PACL)Acl,
ACL_REVISION2,
SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
WorldSid
);
RtlFreeSid( WorldSid );
ASSERT( NT_SUCCESS( NtStatus ) );
if ( !NT_SUCCESS( NtStatus )) {
#if DBG
DbgPrint( "WINREG: Unable to add ACE, NtStatus = %x \n", NtStatus );
#endif
RtlFreeHeap( RtlProcessHeap(), 0, Acl );
return( RtlNtStatusToDosError( NtStatus ) );
}
//
// Build security descriptor
//
NtStatus = RtlCreateSecurityDescriptor( &SecurityDescriptor, SECURITY_DESCRIPTOR_REVISION );
ASSERT( NT_SUCCESS( NtStatus ) );
if ( !NT_SUCCESS( NtStatus )) {
#if DBG
DbgPrint( "WINREG: Unable to create security descriptor, NtStatus = %x \n", NtStatus );
#endif
RtlFreeHeap( RtlProcessHeap(), 0, Acl );
return( RtlNtStatusToDosError( NtStatus ) );
}
#if DBG
if( !RtlValidAcl( (PACL )Acl ) ) {
DbgPrint( "WINREG: Acl is invalid \n" );
RtlFreeHeap( RtlProcessHeap(), 0, Acl );
return( ERROR_INVALID_ACL );
}
#endif
NtStatus = RtlSetDaclSecurityDescriptor ( &SecurityDescriptor,
TRUE,
(PACL)Acl,
FALSE
);
ASSERT( NT_SUCCESS( NtStatus ) );
if ( !NT_SUCCESS( NtStatus )) {
#if DBG
DbgPrint( "WINREG: Unable to set DACL, NtStatus = %x \n", NtStatus );
#endif
RtlFreeHeap( RtlProcessHeap(), 0, Acl );
return( RtlNtStatusToDosError( NtStatus ) );
}
SecurityDescriptorInitialized = TRUE;
return( ERROR_SUCCESS );
}
BOOL
InitializeRegNotifyChangeKeyValue(
)
/*++
Routine Description:
Initializes the static data structures used by the
RegNotifyChangeKeyValue client. Called once at DLL
initialization.
Arguments:
None
Return Value:
BOOLEAN - TRUE if successful.
--*/
{
NTSTATUS NtStatus;
NtStatus = RtlInitializeCriticalSection(
&NotificationCriticalSection
);
if ( NT_SUCCESS( NtStatus ) ) {
//
// Initialize our machine name. Note that the actual
// name is only obtained when the notification API
// is first invoked.
//
OurMachineName.Length = 0;
OurMachineName.MaximumLength = MAX_PATH * sizeof(WCHAR);
OurMachineName.Buffer = OurMachineNameBuffer;
//
// Initialize named pipe data
//
NotificationPipeName.Length = 0;
NotificationPipeName.MaximumLength = MAX_PATH * sizeof(WCHAR);
NotificationPipeName.Buffer = NotificationPipeNameBuffer;
NotificationThread = NULL;
NotificationPipeHandle = NULL;
NotificationPipeSaRpc.RpcSecurityDescriptor.lpSecurityDescriptor = NULL;
return TRUE;
}
return FALSE;
}
BOOL
CleanupRegNotifyChangeKeyValue(
)
/*++
Routine Description:
Performs any cleanup of the static data structures used
by the RegNotifyChangeKeyValue client. Called once at
process termination.
Arguments:
None
Return Value:
BOOLEAN - TRUE if successful.
--*/
{
NTSTATUS NtStatus;
//
// Terminate notification thread if there is one running
//
if ( NotificationThread != NULL ) {
//
// Close the named pipe
//
if ( NotificationPipeHandle != NULL ) {
NtStatus = NtClose( NotificationPipeHandle );
ASSERT( NT_SUCCESS( NtStatus ) );
}
TerminateThread( NotificationThread, 0 );
}
//
// Delete the notification critical section
//
NtStatus = RtlDeleteCriticalSection(
&NotificationCriticalSection
);
ASSERT( NT_SUCCESS( NtStatus ) );
if ( NotificationPipeSaRpc.RpcSecurityDescriptor.lpSecurityDescriptor ) {
RtlFreeHeap(
RtlProcessHeap( ), 0,
NotificationPipeSaRpc.RpcSecurityDescriptor.lpSecurityDescriptor
);
}
return TRUE;
}
#endif // REMOTE_NOTIFICATION_DISABLED
LONG
RegNotifyChangeKeyValue(
HKEY hKey,
BOOL fWatchSubtree,
DWORD dwNotifyFilter,
HANDLE hEvent,
BOOL fAsynchronous
)
/*++
Routine Description:
This API is used to watch a key or sub-tree for changes. It can be
called either synchronously or asynchronously. In the latter case the
caller must supply an event that is signalled when changes occur. In
either case it is possible to filter the criteria by which the
notification occurs.
Arguments:
hKey - Supplies a handle to a key that has been previously opened with
KEY_NOTIFY access.
fWatchSubtree - Supplies a boolean value that if TRUE causes the
system to monitor the key and all of its decsendants. A value of
FALSE causes the system to monitor only the specified key.
dwNotifyFilter - Supplies a set of flags that specify the filter
conditions the system uses to satisfy a change notification.
REG_NOTIFY_CHANGE_KEYNAME - Any key name changes that occur
in a key or subtree being watched will satisfy a
change notification wait. This includes creations
and deletions.
REG_NOTIFY_CHANGE_ATTRIBUTES - Any attribute changes that occur
in a key or subtree being watched will satisfy a
change notification.
REG_NOTIFY_CHANGE_LAST_WRITE - Any last write time changes that
occur in a key or subtree being watched will satisfy a
change notification.
REG_NOTIFY_CHANGE_SECURITY - Any security descriptor changes
that occur in a key or subtree being watched will
satisfy a change notification.
hEvent - Supplies an optional event handle. This parameter is ignored
if fAsynchronus is set to FALSE.
fAsynchronous - Supplies a flag which if FALSE causes the API to not
return until something has changed. If TRUE, the API returns
immediately and changes are reported via the supplied event. It
is an error for this parameter to be TRUE and hEvent to be NULL.
Return Value:
LONG - Returns ERROR_SUCCESS (0); error-code for failure.
Notes:
If the supplied hKey is closed the event is signalled.
Therefore it is possible to return from a wait on the event and then
have subsequent APIs fail.
--*/
{
HKEY Handle;
HANDLE EventHandle;
LONG Error = ERROR_SUCCESS;
NTSTATUS NtStatus;
PRPC_SECURITY_ATTRIBUTES pRpcSa;
HKEY TempHandle = NULL;
#if DBG
if ( BreakPointOnEntry ) {
DbgBreakPoint();
}
#endif
//
// Limit the capabilities associated with HKEY_PERFORMANCE_DATA.
//
if( hKey == HKEY_PERFORMANCE_DATA ) {
return ERROR_INVALID_HANDLE;
}
//
// Validate the dependency between fAsynchronus and hEvent.
//
if (( fAsynchronous ) && ( ! ARGUMENT_PRESENT( hEvent ))) {
return ERROR_INVALID_PARAMETER;
}
Handle = MapPredefinedHandle( hKey, &TempHandle );
if ( Handle == NULL ) {
CLOSE_LOCAL_HANDLE(TempHandle);
return ERROR_INVALID_HANDLE;
}
//
// Notification is not supported on remote handles.
//
if( !IsLocalHandle( Handle ) ) {
CLOSE_LOCAL_HANDLE(TempHandle);
return ERROR_INVALID_HANDLE;
} else {
//
// If its a local handle, make an Nt API call and return.
//
if (IsSpecialClassesHandle( Handle )) {
//
// We call a special function for class keys
//
NtStatus = BaseRegNotifyClassKey(
Handle,
hEvent,
&LocalIoStatusBlock,
dwNotifyFilter,
( BOOLEAN ) fWatchSubtree,
( BOOLEAN ) fAsynchronous
);
} else {
NtStatus = NtNotifyChangeKey(
Handle,
hEvent,
NULL,
NULL,
&LocalIoStatusBlock,
dwNotifyFilter,
( BOOLEAN ) fWatchSubtree,
NULL,
0,
( BOOLEAN ) fAsynchronous
);
}
if( NT_SUCCESS( NtStatus ) ||
( NtStatus == STATUS_PENDING ) ) {
Error = (error_status_t)ERROR_SUCCESS;
} else {
Error = (error_status_t) RtlNtStatusToDosError( NtStatus );
}
CLOSE_LOCAL_HANDLE(TempHandle);
return Error;
}
#ifndef REMOTE_NOTIFICATION_DISABLED
// NOTE: THE FOLLOWING CODE IS DISABLED BY THE CHECK FOR
// IsLocalHandle AT THE BEGINNING OF THE FUNCTION.
//
//
// If this is an asynchronous call, we use the user-provided
// event and will let the user wait on it him/herself.
// Otherwise we have to create our own event and wait on
// it ourselves.
//
// This is because the server side of the API is always
// asynchronous.
//
if ( fAsynchronous ) {
EventHandle = hEvent;
} else {
NtStatus = NtCreateEvent(
&EventHandle,
EVENT_ALL_ACCESS,
NULL,
NotificationEvent,
FALSE
);
if ( !NT_SUCCESS( NtStatus ) ) {
return RtlNtStatusToDosError( NtStatus );
}
}
//
// See if the notification thread is already running
// and create it if not. We have to protect this
// with a critical section because there might be
// several instances of this API doing this check
// at the same time.
//
NtStatus = RtlEnterCriticalSection( &NotificationCriticalSection );
if ( !NT_SUCCESS( NtStatus ) ) {
Error = RtlNtStatusToDosError( NtStatus );
} else {
//
// We are now inside the critical section
//
if ( NotificationThread == NULL ) {
//
// Create a named pipe for the notification thread
// to use.
//
Error = CreateNotificationPipe( );
if ( Error == ERROR_SUCCESS ) {
//
// Create the notification thread
//
NotificationThread = CreateThread(
NULL,
(16 * 1024),
(LPTHREAD_START_ROUTINE)NotificationHandler,
NULL,
0,
&NotificationClientId
);
if ( NotificationThread == NULL ) {
//
// Could not create thread, remove the named pipe.
//
Error = GetLastError();
NtClose( NotificationPipeHandle );
}
}
}
NtStatus = RtlLeaveCriticalSection( &NotificationCriticalSection );
ASSERT( NT_SUCCESS( NtStatus ) );
}
if ( Error == ERROR_SUCCESS ) {
//
// Let the server side do its work. Remember that this call
// is always asynchronous.
//
if ( NotificationPipeSaRpc.RpcSecurityDescriptor.lpSecurityDescriptor ) {
pRpcSa = &NotificationPipeSaRpc;
} else {
pRpcSa = NULL;
}
//NotificationPipeName.Length += sizeof(UNICODE_NULL);
//OurMachineName.Length += sizeof(UNICODE_NULL );
// DbgPrint(" Waiting for notification, handle %x\n", EventHandle );
Error = (LONG)BaseRegNotifyChangeKeyValue(
DereferenceRemoteHandle( Handle ),
(BOOLEAN)fWatchSubtree,
dwNotifyFilter,
(DWORD)EventHandle,
&OurMachineName,
&NotificationPipeName,
pRpcSa
);
//NotificationPipeName.Length -= sizeof(UNICODE_NULL);
//OurMachineName.Length -= sizeof(UNICODE_NULL );
}
//
// If the call went ok. and we are in synchronous mode, we have
// to wait on the event.
//
if ( (Error == ERROR_SUCCESS) && !fAsynchronous ) {
NtStatus = NtWaitForSingleObject(
EventHandle,
FALSE,
NULL
);
if ( !NT_SUCCESS( NtStatus ) ) {
Error = RtlNtStatusToDosError( NtStatus );
}
}
//
// If we created an event, we must close it now.
//
if ( !fAsynchronous ) {
NtStatus = NtClose( EventHandle );
ASSERT( NT_SUCCESS( NtStatus ));
}
return Error;
#endif // REMOTE_NOTIFICATION_DISABLED
}
#ifndef REMOTE_NOTIFICATION_DISABLED
LONG
CreateNotificationPipe(
)
/*++
Routine Description:
Creates the notification named pipe and sets the appropriate
global variables.
Note that the NotificationPipeName set by this function is
server-relative, so that no conversion is required on the
server side.
Arguments:
None
Return Value:
Error code.
--*/
{
UNICODE_STRING PipeName;
WCHAR PipeNameBuffer[ MAX_PATH ];
USHORT OrgSize;
DWORD Sequence;
NTSTATUS NtStatus;
LARGE_INTEGER Timeout;
OBJECT_ATTRIBUTES Obja;
IO_STATUS_BLOCK IoStatusBlock;
DWORD MachineNameLength;
LONG WinStatus;
//
// Get our machine name
//
MachineNameLength = MAX_PATH;
if ( !GetComputerNameW( OurMachineNameBuffer, &MachineNameLength ) ) {
return GetLastError();
}
OurMachineName.Buffer = OurMachineNameBuffer;
OurMachineName.Length = (USHORT)(MachineNameLength * sizeof(WCHAR));
OurMachineName.MaximumLength = (USHORT)(MAX_PATH * sizeof(WCHAR));
//
// Get the "here" name
//
RtlMoveMemory(
PipeNameBuffer,
NAMED_PIPE_HERE,
sizeof( NAMED_PIPE_HERE)
);
PipeName.MaximumLength = MAX_PATH * sizeof(WCHAR);
PipeName.Buffer = PipeNameBuffer;
//
// Remember the size of the base portion of the pipe name, so
// we can patch it later when we attempt to create the full
// name.
//
OrgSize = (USHORT)(sizeof(NAMED_PIPE_HERE) - sizeof(UNICODE_NULL));
//
// Create the named pipe, if the name is already being used,
// keep trying with different names.
//
Sequence = 0;
Timeout.QuadPart = Int32x32To64( -10 * 1000, 50 );
//
// Initialize the security descriptor that will be set in the named pipe
//
WinStatus = InitializeNotificationPipeSecurityDescriptor();
if( WinStatus != ERROR_SUCCESS ) {
return( WinStatus );
}
do {
//
// Get a semi-unique name
//
if ( !MakeSemiUniqueName( &NotificationPipeName, Sequence++ ) ) {
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
break;
}
//
// Patch the full pipe name, in case this is not our first
// try.
//
PipeName.Buffer[OrgSize/sizeof(WCHAR)] = UNICODE_NULL;
PipeName.Length = OrgSize;
//
// Now get the full path of the pipe name
//
NtStatus = RtlAppendUnicodeStringToString(
&PipeName,
&NotificationPipeName
);
ASSERT( NT_SUCCESS( NtStatus ) );
if ( !NT_SUCCESS( NtStatus ) ) {
break;
}
InitializeObjectAttributes(
&Obja,
&PipeName,
OBJ_CASE_INSENSITIVE,
NULL,
NULL
);
if( SecurityDescriptorInitialized ) {
Obja.SecurityDescriptor = &SecurityDescriptor;
}
NtStatus = NtCreateNamedPipeFile (
&NotificationPipeHandle,
SYNCHRONIZE | GENERIC_READ | FILE_WRITE_ATTRIBUTES,
&Obja,
&IoStatusBlock,
FILE_SHARE_WRITE | FILE_SHARE_READ,
FILE_CREATE,
FILE_SYNCHRONOUS_IO_NONALERT,
FILE_PIPE_MESSAGE_TYPE,
FILE_PIPE_MESSAGE_MODE,
FILE_PIPE_QUEUE_OPERATION,
1,
0,
0,
&Timeout
);
} while ( (NtStatus == STATUS_OBJECT_NAME_EXISTS) &&
(Sequence <= MAX_PIPE_RETRIES )
);
//
// At this point we don't need the security descriptor anymore.
// Free the memory allocated for the ACL
//
if( SecurityDescriptorInitialized ) {
RtlFreeHeap( RtlProcessHeap( ), 0, Acl );
Acl = NULL;
SecurityDescriptorInitialized = FALSE;
}
if ( !NT_SUCCESS( NtStatus ) ) {
return RtlNtStatusToDosError( NtStatus );
}
NotificationPipeName.Length += sizeof(UNICODE_NULL);
OurMachineName.Length += sizeof(UNICODE_NULL );
return ERROR_SUCCESS;
}
VOID
NotificationHandler(
)
/*++
Routine Description:
This function is the entry point of the notification thread.
The notification thread is created the first time that
the RegNotifyChangeKeyValue API is called by the process,
and keeps on running until the process terminates.
This function creates a named pipe whose name is given by
RegNotifyChangeKeyValue to all its servers. The servers
then use the pipe to indicate that a particular event has
to be signaled.
117
Note that this single thread is in charge of signaling the
events for all the RegNotifyChangeKeyValue invocations of
the process. However no state has to be maintained by this
thread because all the state information is provided by the
server through the named pipe.
Arguments:
None
Return Value:
None
--*/
{
NTSTATUS NtStatus;
IO_STATUS_BLOCK IoStatusBlock;
HANDLE EventHandle;
ASSERT( NotificationPipeHandle != NULL );
while ( TRUE ) {
//
// Wait for a connection
//
NtStatus = NtFsControlFile(
NotificationPipeHandle,
NULL,
NULL,
NULL,
&IoStatusBlock,
FSCTL_PIPE_LISTEN,
NULL,
0,
NULL,
0
);
if ( NtStatus == STATUS_PENDING ) {
NtStatus = NtWaitForSingleObject(
NotificationPipeHandle,
FALSE,
NULL
);
}
if ( NT_SUCCESS( NtStatus ) ||
( NtStatus == STATUS_PIPE_CONNECTED ) ) {
//
// Read an event handle from the pipe
//
NtStatus = NtReadFile(
NotificationPipeHandle,
NULL,
NULL,
NULL,
&IoStatusBlock,
( PVOID )&EventHandle,
sizeof( HANDLE ),
NULL,
NULL
);
if ( NtStatus == STATUS_PENDING ) {
NtStatus = NtWaitForSingleObject(
NotificationPipeHandle,
FALSE,
NULL
);
}
//
// Signal the Event.
//
if ( NT_SUCCESS( NtStatus ) ) {
ASSERT( IoStatusBlock.Information == sizeof( HANDLE ) );
//
// Signal the event
//
//DbgPrint(" WINREG: Signaling handle %x\n", EventHandle );
NtStatus = NtSetEvent( EventHandle, NULL );
#if DBG
if ( !NT_SUCCESS( NtStatus ) ) {
DbgPrint( "WINREG: Cannot signal notification event 0x%x, status %x\n",
EventHandle, NtStatus );
}
#endif
ASSERT( NT_SUCCESS( NtStatus ) );
} else if ( NtStatus != STATUS_PIPE_BROKEN ) {
#if DBG
DbgPrint( "WINREG (Notification handler) error reading pipe\n" );
DbgPrint( " status 0x%x\n", NtStatus );
#endif
ASSERT( NT_SUCCESS( NtStatus ) );
}
} else if ( NtStatus != STATUS_PIPE_BROKEN &&
NtStatus != STATUS_PIPE_CLOSING) {
#if DBG
DbgPrint( "WINREG (Notification): FsControlFile (Connect) status 0x%x\n",
NtStatus );
#endif
}
if ( NT_SUCCESS( NtStatus ) ||
NtStatus == STATUS_PIPE_BROKEN ||
NtStatus == STATUS_PIPE_CLOSING ||
NtStatus == STATUS_PIPE_LISTENING ||
NtStatus == STATUS_PIPE_BUSY ) {
//
// Disconnect
//
NtStatus = NtFsControlFile(
NotificationPipeHandle,
NULL,
NULL,
NULL,
&IoStatusBlock,
FSCTL_PIPE_DISCONNECT,
NULL,
0,
NULL,
0
);
if ( NtStatus == STATUS_PENDING) {
NtStatus = NtWaitForSingleObject(
NotificationPipeHandle,
FALSE,
NULL
);
}
#if DBG
if ( !NT_SUCCESS( NtStatus ) ) {
DbgPrint( "WINREG (Notification): FsControlFile (Disconnect) status 0x%x\n",
NtStatus );
}
#endif
ASSERT( NT_SUCCESS( NtStatus ) );
}
}
}
#endif // REMOTE_NOTIFICATION_DISABLED