/*++ Copyright (c) 1989 Microsoft Corporation Module Name: uuid.c Abstract: This module implements the core time and sequence number allocation for UUIDs. Author: Mario Goertzel (MarioGo) 22-Nov-1994 Revision History: --*/ #include "exp.h" // // Well known values // #define RPC_SEQUENCE_NUMBER_PATH L"\\Registry\\Machine\\Software\\Microsoft\\Rpc" #define RPC_SEQUENCE_NUMBER_NAME L"UuidSequenceNumber" // // Global variables // LARGE_INTEGER ExpUuidLastTimeAllocated; ULONG ExpUuidSequenceNumber; BOOLEAN ExpUuidSequenceNumberValid; BOOLEAN ExpUuidSequenceNumberNotSaved; ERESOURCE ExpUuidLock; ERESOURCE ExpSequenceLock; // // Helper functions to load and save the Uuid sequence number. // extern NTSTATUS ExpUuidLoadSequenceNumber( OUT PULONG ); extern NTSTATUS ExpUuidSaveSequenceNumber( IN ULONG ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, ExpUuidLoadSequenceNumber) #pragma alloc_text(PAGE, ExpUuidSaveSequenceNumber) #pragma alloc_text(INIT, ExpUuidInitialization) #pragma alloc_text(PAGE, NtAllocateUuids) #endif NTSTATUS ExpUuidLoadSequenceNumber( OUT PULONG Sequence ) /*++ Routine Description: This function loads the saved sequence number from the registry. If no sequence number is found in the registry, it creates a 'random' one and saves that in the registry. This function is called only during system startup. Arguments: Sequence - Pointer to storage for the sequence number. Return Value: STATUS_SUCCESS when the sequence number is successfully read from the registry. STATUS_UNSUCCESSFUL when the sequence number is not correctly stored in the registry. Failure codes from ZwOpenKey() and ZwQueryValueKey() maybe returned. --*/ { NTSTATUS Status; OBJECT_ATTRIBUTES ObjectAttributes; UNICODE_STRING KeyPath, KeyName; HANDLE Key; CHAR KeyValueBuffer[sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(ULONG)]; PKEY_VALUE_PARTIAL_INFORMATION KeyValueInformation; ULONG ResultLength; PAGED_CODE(); KeyValueInformation = (PKEY_VALUE_PARTIAL_INFORMATION)KeyValueBuffer; RtlInitUnicodeString(&KeyPath, RPC_SEQUENCE_NUMBER_PATH); RtlInitUnicodeString(&KeyName, RPC_SEQUENCE_NUMBER_NAME); InitializeObjectAttributes( &ObjectAttributes, &KeyPath, OBJ_CASE_INSENSITIVE, NULL, NULL ); Status = ZwOpenKey( &Key, GENERIC_READ, &ObjectAttributes ); if (NT_SUCCESS(Status)) { Status = ZwQueryValueKey( Key, &KeyName, KeyValuePartialInformation, KeyValueInformation, sizeof(KeyValueBuffer), &ResultLength ); ZwClose( Key ); } if (NT_SUCCESS(Status)) { if ( KeyValueInformation->Type == REG_DWORD && KeyValueInformation->DataLength == sizeof(ULONG) ) { *Sequence = *(PULONG)KeyValueInformation->Data; } else { Status = STATUS_UNSUCCESSFUL; } } return(Status); } NTSTATUS ExpUuidSaveSequenceNumber( IN ULONG Sequence ) /*++ Routine Description: This function save the uuid sequence number in the registry. This value will be read by ExpUuidLoadSequenceNumber during the next boot. Arguments: Sequence - The sequence number to save. Return Value: STATUS_SUCCESS Failure codes from ZwOpenKey() and ZwSetValueKey() maybe returned. --*/ { NTSTATUS Status; OBJECT_ATTRIBUTES ObjectAttributes; UNICODE_STRING KeyPath, KeyName; HANDLE Key; PAGED_CODE(); RtlInitUnicodeString(&KeyPath, RPC_SEQUENCE_NUMBER_PATH); RtlInitUnicodeString(&KeyName, RPC_SEQUENCE_NUMBER_NAME); InitializeObjectAttributes( &ObjectAttributes, &KeyPath, OBJ_CASE_INSENSITIVE, NULL, NULL ); Status = ZwOpenKey( &Key, GENERIC_READ, &ObjectAttributes ); if (NT_SUCCESS(Status)) { Status = ZwSetValueKey( Key, &KeyName, 0, REG_DWORD, &Sequence, sizeof(ULONG) ); ZwClose( Key ); } return(Status); } BOOLEAN ExpUuidInitialization ( VOID ) /*++ Routine Description: This function initializes the UUID allocation. Arguments: None. Return Value: A value of TRUE is returned if the initialization is successfully completed. Otherwise, a value of FALSE is returned. --*/ { NTSTATUS Status; PAGED_CODE(); ExInitializeResource(&ExpUuidLock); ExInitializeResource(&ExpSequenceLock); ExpUuidSequenceNumberValid = FALSE; // We can use the current time since we'll be changing the sequence number. KeQuerySystemTime(&ExpUuidLastTimeAllocated); return TRUE; } NTSTATUS NtAllocateUuids ( OUT PULARGE_INTEGER Time, OUT PULONG Range, OUT PULONG Sequence ) /*++ Routine Description: This function reserves a range of time for the caller(s) to use for handing out Uuids. As far a possible the same range of time and sequence number will never be given out. (It's possible to reboot 2^14-1 times and set the clock backwards and then call this allocator and get a duplicate. Since only the low 14bits of the sequence number are used in a real uuid.) Arguments: Time - Supplies the address of a variable that will receive the start time (SYSTEMTIME format) of the range of time reserved. Range - Supplies the address of a variable that will receive the number of ticks (100ns) reserved after the value in Time. The range reserved is *Time to (*Time + *Range - 1). Sequence - Supplies the address of a variable that will receive the time sequence number. This value is used with the associated range of time to prevent problems with clocks going backwards. Return Value: STATUS_SUCCESS is returned if the service is successfully executed. STATUS_RETRY is returned if we're unable to reserve a range of UUIDs. This may (?) occur if system clock hasn't advanced and the allocator is out of cached values. STATUS_ACCESS_VIOLATION is returned if the output parameter for the UUID cannot be written. STATUS_UNSUCCESSFUL is returned if some other service reports an error, most likly the registery. --*/ { KPROCESSOR_MODE PreviousMode; NTSTATUS Status; LARGE_INTEGER CurrentTime; LARGE_INTEGER AvailableTime; LARGE_INTEGER OutputTime; ULONG OutputRange; ULONG OutputSequence; PAGED_CODE(); // // Establish an exception handler and attempt to write the output // arguments. If the write attempt fails, then return // the exception code as the service status. Otherwise return success // as the service status. // try { // // Get previous processor mode and probe arguments if necessary. // PreviousMode = KeGetPreviousMode(); if (PreviousMode != KernelMode) { ProbeForWrite((PVOID)Time, sizeof(LARGE_INTEGER), sizeof(ULONG)); ProbeForWrite((PVOID)Range, sizeof(ULONG), sizeof(ULONG)); ProbeForWrite((PVOID)Sequence, sizeof(ULONG), sizeof(ULONG)); } } except (ExSystemExceptionFilter()) { return GetExceptionCode(); } ExAcquireResourceExclusive(&ExpUuidLock, TRUE); // // Make sure we have a valid sequence number. If not, make one up. // if (ExpUuidSequenceNumberValid == FALSE) { Status = ExpUuidLoadSequenceNumber(&ExpUuidSequenceNumber); if (!NT_SUCCESS(Status)) { // Unable read the sequence number, this means we should make one up. LARGE_INTEGER PerfCounter; LARGE_INTEGER PerfFrequency; // This should only happen when NtAllocateUuids() is called // for the first time on a given machine. (machine, not boot) KdPrint(("Uuid: Generating first sequence number.\n")); PerfCounter = KeQueryPerformanceCounter(&PerfFrequency); ExpUuidSequenceNumber ^= (ULONG)&Status ^ PerfCounter.LowPart ^ PerfCounter.HighPart ^ (ULONG)Sequence; } else { // We increment the sequence number on every boot. ExpUuidSequenceNumber++; } ExpUuidSequenceNumberValid = TRUE; ExpUuidSequenceNumberNotSaved = TRUE; } // // Get the current time, usually we will have plenty of avaliable // to give the caller. But we may need to deal with time going // backwards and really fast machines. // KeQuerySystemTime(&CurrentTime); AvailableTime.QuadPart = CurrentTime.QuadPart - ExpUuidLastTimeAllocated.QuadPart; if (AvailableTime.QuadPart < 0) { // Time has been set time backwards. This means that we must make sure // that somebody increments the sequence number and saves the new // sequence number in the registry. ExpUuidSequenceNumberNotSaved = TRUE; ExpUuidSequenceNumber++; // The sequence number has been changed, so it's now okay to set time // backwards. Since time is going backwards anyway, it's okay to set // it back an extra millisecond or two. ExpUuidLastTimeAllocated.QuadPart = CurrentTime.QuadPart - 20000; AvailableTime.QuadPart = 20000; } if (AvailableTime.QuadPart == 0) { // System time hasn't moved. The caller should yield the CPU and retry. ExReleaseResource(&ExpUuidLock); return(STATUS_RETRY); } // // Common case, time has moved forward. // if (AvailableTime.QuadPart > 10*1000*1000) { // We never want to give out really old (> 1 second) Uuids. AvailableTime.QuadPart = 10*1000*1000; } if (AvailableTime.QuadPart > 10000) { // We've got over a millisecond to give out. We'll save some time for // another caller so that we can avoid returning STATUS_RETRY very offen. OutputRange = 10000; AvailableTime.QuadPart -= 10000; } else { // Not much time avaiable, give it all away. OutputRange = (ULONG)AvailableTime.QuadPart; AvailableTime.QuadPart = 0; } OutputTime.QuadPart = CurrentTime.QuadPart - (OutputRange + AvailableTime.QuadPart); ExpUuidLastTimeAllocated.QuadPart = OutputTime.QuadPart + OutputRange; // Last time allocated is just after the range we hand back to the caller // this may be almost a second behind the true system time. OutputSequence = ExpUuidSequenceNumber; ExReleaseResource(&ExpUuidLock); // Saving the sequence number will usually complete without any problems. // So we let any other threads go at this point. If the save fails, // we'll retry on some future call. if (ExpUuidSequenceNumberNotSaved == TRUE) { if (ExAcquireResourceExclusive(&ExpSequenceLock, FALSE) == TRUE) { if (ExpUuidSequenceNumberNotSaved == TRUE) { ExpUuidSequenceNumberNotSaved = FALSE; // Print this message just to make sure we aren't hitting the // registry too much under normal usage. KdPrint(("Uuid: Saving new sequence number.\n")); Status = ExpUuidSaveSequenceNumber(ExpUuidSequenceNumber); if (!NT_SUCCESS(Status)) { ExpUuidSequenceNumberNotSaved = TRUE; } } } ExReleaseResource(&ExpSequenceLock); } // // Attempt to store the result of this call into the output parameters. // This is done within an exception handler in case output parameters // are now invalid. // try { Time->QuadPart = OutputTime.QuadPart; *Range = OutputRange; *Sequence = OutputSequence; } except (ExSystemExceptionFilter()) { return GetExceptionCode(); } return(STATUS_SUCCESS); }