/*++ 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 #include "regrpc.h" #include "client.h" #include 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