/*++ Copyright (C) 2002 Microsoft Corporation Module Name: registry.c Abstract: This module contains routines that port drivers export to miniports to allow them to read and write registry data. The Keys are relative to the miniport's \Parameters key. Author: Environment: kernel mode only Notes: Revision History: --*/ #include "precomp.h" #define MAX_REGISTRY_BUFFER_SIZE 0x10000 #define PORT_TAG_REGISTRY_BUFFER 'BRlP' #define PORT_REGISTRY_INFO_INITIALIZED 0x00000001 #define PORT_REGISTRY_BUFFER_ALLOCATED 0x00000002 NTSTATUS PortMiniportRegistryInitialize( IN OUT PPORT_REGISTRY_INFO PortContext ) /*++ Routine Description: This routine is called by the port driver to init the registry routine's internal data. Arguments: PortContext - Used to identify the caller and instatiation of the caller. Return Value: STATUS_SUCCESS for now. If this is extended, it might require allocations, etc. --*/ { NTSTATUS status; ASSERT(PortContext->Size == sizeof(PORT_REGISTRY_INFO)); // // Initialize the spinlock and list entry. // KeInitializeSpinLock(&PortContext->SpinLock); InitializeListHead(&PortContext->ListEntry); // // Ensure that buffer is NULL at this time. It's only valid after we allocate it. // PortContext->Buffer = NULL; // // Indiacate that this is ready to go. // PortContext->Flags = PORT_REGISTRY_INFO_INITIALIZED; return STATUS_SUCCESS; } VOID PortMiniportRegistryDestroy( IN PPORT_REGISTRY_INFO PortContext ) /*++ Routine Description: This routine is called by the port driver when it unloads a miniport. It will free the resources and cleanup whatever else. Arguments: PortContext - Used to identify the caller and instatiation of the caller. Return Value: NOTHING --*/ { KIRQL irql; KeAcquireSpinLock(&PortContext->SpinLock, &irql); // // See whether there is still a buffer hanging around. // if (PortContext->Flags & PORT_REGISTRY_BUFFER_ALLOCATED) { ASSERT(PortContext->Buffer); // // Free it. // ExFreePool(PortContext->Buffer); } else { // // This should be NULL if it's not allocated. // ASSERT(PortContext->Buffer == NULL); } // // Clean up. // PortContext->Flags = 0; PortContext->Buffer = NULL; KeReleaseSpinLock(&PortContext->SpinLock, irql); return; } NTSTATUS PortAllocateRegistryBuffer( IN PPORT_REGISTRY_INFO PortContext ) /*++ Routine Description: This routine is called by the port driver to allocate a registry buffer of Length for the miniport. The caller should initialize Length before calling this routine. Length is checked against a max. and alignment requirements and updated accordingly, if necessary. Arguments: PortContext - Value used to identify the caller and instatiation of the caller. Return Value: The buffer field is updated or NULL if some error prevents the allocation. Length is updated to reflect the actual size. SUCCESS, INSUFFICIENT_RESOURCES, or BUSY if the miniport already has a buffer. --*/ { PUCHAR buffer = NULL; ULONG length; NTSTATUS status; // // The size must be correct. // ASSERT(PortContext->Size == sizeof(PORT_REGISTRY_INFO)); // // Must be initialized via the init routine. // ASSERT(PortContext->Flags & PORT_REGISTRY_INFO_INITIALIZED ); // // Can't be here twice. // ASSERT((PortContext->Flags & PORT_REGISTRY_BUFFER_ALLOCATED) == 0); // // Capture the length. // length = PortContext->LengthNeeded; // // See if the miniport is attempted a double allocate. // if (PortContext->Flags & PORT_REGISTRY_BUFFER_ALLOCATED) { // // This would say that there better be a buffer. // ASSERT(PortContext->Buffer); // // Buffer already outstanding, so don't allow this. // status = STATUS_DEVICE_BUSY; } else { // // Check the upper bound. // if (length > MAX_REGISTRY_BUFFER_SIZE) { // // The request is too large, reset it. The port driver or miniport will have // to deal with the change. // length = MAX_REGISTRY_BUFFER_SIZE; } // // Allocate the buffer. // buffer = ExAllocatePoolWithTag(NonPagedPool, length, PORT_TAG_REGISTRY_BUFFER); if (buffer == NULL) { // // Set the caller's length to 0 as the allocation failed. // PortContext->AllocatedLength = 0; status = STATUS_INSUFFICIENT_RESOURCES; } else { // // Indicate that the allocation of Length was successful, and // that the miniport has a registry buffer. // PortContext->Flags |= PORT_REGISTRY_BUFFER_ALLOCATED; PortContext->Buffer = buffer; PortContext->AllocatedLength = length; // // Zero it for them to be nice. // RtlZeroMemory(buffer, length); status = STATUS_SUCCESS; } } return status; } NTSTATUS PortFreeRegistryBuffer( IN PPORT_REGISTRY_INFO PortContext ) /*++ Routine Description: Frees the buffer allocated via PortAllocateRegistryBuffer. Arguments: PortContext - Value used to identify the caller and instatiation of the caller. Return Value: INVALID_PARAMETER if the buffer isn't already allocated. SUCCESS --*/ { // // The size must be correct. // ASSERT(PortContext->Size == sizeof(PORT_REGISTRY_INFO)); // // Must be initialized via the init routine. // ASSERT(PortContext->Flags & PORT_REGISTRY_INFO_INITIALIZED ); // // Can't be here, unless a buffer has been allocated. // ASSERT(PortContext->Flags & PORT_REGISTRY_BUFFER_ALLOCATED); // // If it's not allocated, return an error. // if ((PortContext->Flags & PORT_REGISTRY_BUFFER_ALLOCATED) == 0) { ASSERT(PortContext->Buffer == NULL); return STATUS_INVALID_PARAMETER; } // // Poison the buffer to catch poorly-written miniports. // RtlZeroMemory(PortContext->Buffer, PortContext->AllocatedLength); // // Free the buffer. // ExFreePool(PortContext->Buffer); // // Indicate that it's gone. // PortContext->Flags &= ~PORT_REGISTRY_BUFFER_ALLOCATED; PortContext->AllocatedLength = 0; PortContext->Buffer = NULL; return STATUS_SUCCESS; } ULONG WCharToAscii( OUT PUCHAR Destination, IN PWCHAR Source, IN ULONG BufferSize ) /*+++ Routine Description: This routine is used to convert the Source buffer into ASCII. NOTE: BufferSize should include the NULL-Terminator, while the return value does not. Arguements: Destination - ASCII buffer to place the converted values. Source - WCHAR buffer containing the string to convert. BufferSize - Size, in bytes, of Destination. Return Value: The converted buffer and the count of converted chars. The NULL-termination isn't included. --*/ { ULONG convertedCount = 0; ULONG i; RtlZeroMemory(Destination, BufferSize); if (Source) { // // Run through the Source buffer and convert the WCHAR to ASCII, placing // the converted value in Destination. Ensure that the destination buffer // isn't overflowed. // for (i = 0; (i < (BufferSize - 1)) && (*Source); i++, convertedCount++) { *Destination = (UCHAR)*Source; Destination++; Source++; } } return convertedCount; } ULONG AsciiToWChar( IN PWCHAR Destination, IN PUCHAR Source, IN ULONG BufferSize ) /*+++ Routine Description: This routine is used to convert the Source buffer into WCHAR. NOTE: BufferSize should include the NULL-Terminator, while the return value does not. Arguements: Destination - WCHAR buffer to place the converted values. Source - ASCII buffer containing the string to convert. BufferSize - Size, in bytes, of Destination. Return Value: The converted buffer and the count of converted chars. The NULL-termination isn't included. --*/ { ULONG convertedCount = 0; ULONG i; RtlZeroMemory(Destination, BufferSize); if (Source) { // // Run through source buffer and convert the ascii values to WCHAR and put // the convert into Destination. Ensure that neither source nor dest are // overflowed. // for (i = 0; (i < (BufferSize - 1)) && (*Source); i++, convertedCount++) { *Destination = (WCHAR)*Source; Destination++; Source++; } } return convertedCount; } NTSTATUS PortAsciiToUnicode( IN PUCHAR AsciiString, OUT PUNICODE_STRING UnicodeString ) { ANSI_STRING ansiString; // // Convert ValueName to Unicode. // RtlInitAnsiString(&ansiString, AsciiString); return RtlAnsiStringToUnicodeString(UnicodeString, &ansiString, TRUE); } NTSTATUS PortpBinaryReadCallBack( IN PWSTR ValueName, IN ULONG Type, IN PVOID Buffer, IN ULONG BufferLength, IN PVOID Context, IN PVOID EntryContext ) /*++ Routine Description: This routine is the callback function for handling REG_BINARY reads. It will determine whether the buffer in the PORT_REGISTRY_INFO is of sufficient size to handle the data, and copy it over, if necessary. Otherwise the data length needed is updated. Arguments: ValueName - The name of the data to be returned. Type - The reg. data type for this request. ValueLength - Size, in bytes, of ValueData Context - Not used. PortContext - Blob containing the miniports buffer and it's size. Return Value: SUCCESS or BUFFER_TOO_SMALL (which unfortunately gets updated by the RTL function to SUCCESS. InternalStatus is updated to the real status and the length field within the REGISTRY_INFO struct. is updated. --*/ { PPORT_REGISTRY_INFO portContext = EntryContext; PUCHAR currentBuffer; NTSTATUS status = STATUS_BUFFER_TOO_SMALL; // // Determine whether the supplied buffer is big enough to hold the data. // if (portContext->CurrentLength >= BufferLength) { // // Determine the correct offset into the buffer. // currentBuffer = portContext->Buffer + portContext->Offset; // // The Rtl routine will free it's buffer, so get the data now. // RtlCopyMemory(currentBuffer, Buffer, BufferLength); // // Update the status to show the data in the buffer is valid. // status = STATUS_SUCCESS; } // // Update the length. This will either tell them how big the buffer // should be, or how large the returned data actually is. // The Read routine will handle the rest. // portContext->CurrentLength = BufferLength; portContext->InternalStatus = status; // // Return the status to the Read routine. // return status; } NTSTATUS PortRegistryRead( IN PUNICODE_STRING RegistryKeyName, IN PUNICODE_STRING ValueName, IN ULONG Type, IN PPORT_REGISTRY_INFO PortContext ) /*++ Routine Description: This routine is used by the portdriver to read the info. at ValueName from the regkey This assumes that the data is a REG_SZ, REG_DWORD, or REG_BINARY only. REG_SZ data is converted into a NULL-terminiated ASCII string from the UNICODE. DWORD and BINARY data are directly copied into the caller's buffer if it's of correct size. Arguments: RegistryKeyName - The absolute key name where ValueName lives. ValueName - The name of the data to be returned. Type - The reg. data type for this request. PortContext - Blob containing the miniports buffer and it's size. Return Value: STATUS of the registry routines, INSUFFICIENT_RESOURCES, or BUFFER_TOO_SMALL. If TOO_SMALL, LengthNeeded within the PortContext is updated to reflect the size needed. --*/ { RTL_QUERY_REGISTRY_TABLE queryTable[2]; WCHAR defaultData[] = { L"\0" }; ULONG defaultUlong = (ULONG)-1; UNICODE_STRING unicodeString; NTSTATUS status; ULONG length; PUCHAR currentBuffer; RtlZeroMemory(queryTable, sizeof(queryTable)); // // Calculate the actual buffer location where this data should go. // This presupposes that the port-driver has done all the validation, otherwise // this will be blindly copying into who-knows-where. // currentBuffer = PortContext->Buffer + PortContext->Offset; // // Looking for what lives at ValueName. // queryTable[0].Name = ValueName->Buffer; // // Indicate that there is no call-back routine and to return everything as one big // blob. // queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_NOEXPAND; // // Handle setting up for the various types that are supported. // if (Type == REG_SZ) { RtlZeroMemory(&unicodeString, sizeof(UNICODE_STRING)); // // Local storage for the returned data. The routine will allocate the buffer // and set Length. // queryTable[0].EntryContext = &unicodeString; queryTable[0].DefaultData = defaultData; queryTable[0].DefaultLength = sizeof(defaultData); } else if (Type == REG_DWORD) { // // The data will be placed in the first ulong of the caller's // buffer. // queryTable[0].EntryContext = currentBuffer; queryTable[0].DefaultData = &defaultUlong; queryTable[0].DefaultLength = sizeof(ULONG); } else { // // Clear the flags because we need a callback to determine // the real size of the binary data. // queryTable[0].Flags = 0; queryTable[0].QueryRoutine = PortpBinaryReadCallBack; queryTable[0].EntryContext = PortContext; queryTable[0].DefaultData = &defaultUlong; queryTable[0].DefaultLength = sizeof(ULONG); } // // Set the type. // queryTable[0].DefaultType = Type; // // Call the query routine. This will either be direct, or result in the callback // function getting invoked. // status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE, RegistryKeyName->Buffer, queryTable, NULL, NULL); if (NT_SUCCESS(status)) { if (Type == REG_SZ) { // // Have their data. Now figure out whether it fits. The Query function allocates // the unicode string buffer. // if ((unicodeString.Length) && ((unicodeString.Length / sizeof(WCHAR)) <= PortContext->CurrentLength)) { // // Magically convert. // length = WCharToAscii(currentBuffer, unicodeString.Buffer, PortContext->CurrentLength); // // Set the length of the buffer. // PortContext->CurrentLength = length; } else { ASSERT(unicodeString.Length); // // Update the Length to indicate how big it should actually be. // PortContext->LengthNeeded = (unicodeString.Length + 1) / sizeof(WCHAR); PortContext->CurrentLength = 0; status = STATUS_BUFFER_TOO_SMALL; } // // Free our string, as the data has already been copied, or won't be copied // into the caller's buffer. // ExFreePool(unicodeString.Buffer); } else if (Type == REG_DWORD) { // // The data should already be there. // PortContext->CurrentLength = sizeof(ULONG); } else { // // The Rtl routine has this annoying effect of fixing up BUFFER_TOO_SMALL // into SUCCESS. Check for this case. // if (PortContext->InternalStatus == STATUS_BUFFER_TOO_SMALL) { // // Reset the status correctly for the caller. // status = PortContext->InternalStatus; // // Update length needed, so that the miniport can realloc. // PortContext->LengthNeeded = PortContext->CurrentLength; PortContext->CurrentLength = 0; } else { // // The callback did all the necessary work. // NOTHING; } } } else { // // Indicate to the caller that the error is NOT due to a length mismatch or // that the Buffer is too small. The difference is that if too small, the callback // routine adjusted CurrentLength. // PortContext->LengthNeeded = PortContext->CurrentLength; PortContext->CurrentLength = 0; } return status; } NTSTATUS PortRegistryWrite( IN PUNICODE_STRING RegistryKeyName, IN PUNICODE_STRING ValueName, IN ULONG Type, IN PPORT_REGISTRY_INFO PortContext ) /*++ Routine Description: This routine is used by the port-driver to write the contents of Buffer to ValueName which is located at the reg. key RegistryKeyName. Buffer is first converted to UNICODE then the write takes place. Arguments: RegistryKeyName - The absolute path to the key name. ValueName - The name of the data to be written. Type - The reg. data type for this operation. PortContext - Blob containing the miniports buffer and it's size. Return Value: STATUS from the registry routines, or INSUFFICIENT_RESOURCES --*/ { UNICODE_STRING unicodeString; ULONG bufferLength; PUCHAR currentBuffer; LONG offset; ULONG length; NTSTATUS status; // // Determine whether the field exists. // status = RtlCheckRegistryKey(RTL_REGISTRY_ABSOLUTE, RegistryKeyName->Buffer); if (!NT_SUCCESS(status)) { // // The key doesn't exist. Create it. // status = RtlCreateRegistryKey(RTL_REGISTRY_ABSOLUTE, RegistryKeyName->Buffer); } if (!NT_SUCCESS(status)) { // // Can't go on. Return the error to the port-driver and it can figure // out what best to do. // return status; } // // Calculate the actual buffer location where this data lives. // This presupposes that the port-driver has done all the validation. // currentBuffer = PortContext->Buffer + PortContext->Offset; if (Type == REG_SZ) { // // Determine the size needed for the WCHAR. // bufferLength = PortContext->CurrentLength * sizeof(WCHAR); // // Allocate a buffer to build the converted data in. // unicodeString.Buffer = ExAllocatePool(NonPagedPool, bufferLength + sizeof(UNICODE_NULL)); if (unicodeString.Buffer == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(unicodeString.Buffer, bufferLength + sizeof(UNICODE_NULL)); // // Set the lengths. // unicodeString.MaximumLength = (USHORT)(bufferLength + sizeof(UNICODE_NULL)); unicodeString.Length = (USHORT)bufferLength; // // Convert it. // length = AsciiToWChar(unicodeString.Buffer, currentBuffer, unicodeString.Length); // // Length is now set for the call below. Get the buffer by resetting // currentbuffer to that of the unicode string's. // currentBuffer = (PUCHAR)unicodeString.Buffer; } else if (Type == REG_DWORD){ // // always this size. // length = sizeof(ULONG); } else { // // For BINARY use the passed in buffer (currentBuffer) and length. // length = PortContext->CurrentLength; } // // Write the data to the specified key/Value // status = RtlWriteRegistryValue(RTL_REGISTRY_ABSOLUTE, RegistryKeyName->Buffer, ValueName->Buffer, Type, currentBuffer, length); return status; } NTSTATUS PortBuildRegKeyName( IN PUNICODE_STRING RegistryPath, IN OUT PUNICODE_STRING KeyName, IN ULONG PortNumber, IN ULONG Global ) /*++ Routine Description: This routine will build the registry keyname to the miniport's Device(N) key based on RegistryPath and whether the key is for global miniport data, or specific to one scsiN. Arguments: RegistryPath - The path to the miniport's service key. KeyName - Storage for the whole path. PortNumber - The adapter ordinal. Valid only if Global is FALSE. Global - Indicates whether the Device or Device(N) path should be built. Return Value: SUCCESS - KeyName is valid. INSUFFICIENT_RESOURCES --*/ { UNICODE_STRING unicodeValue; UNICODE_STRING tempKeyName; ANSI_STRING ansiKeyName; ULONG maxLength; NTSTATUS status; UCHAR paramsBuffer[24]; // // If this is global, it represents ALL adapters being controlled by // the miniport. Otherwise, it's the scsiN key only. // if (Global) { RtlInitAnsiString(&ansiKeyName, "\\Parameters\\Device"); RtlAnsiStringToUnicodeString(&tempKeyName, &ansiKeyName, TRUE); } else { // // Get the scsiport'N'. // sprintf(paramsBuffer, "\\Parameters\\Device%d", PortNumber); RtlInitAnsiString(&ansiKeyName, paramsBuffer); RtlAnsiStringToUnicodeString(&tempKeyName, &ansiKeyName, TRUE); } // // The total length will be the size of the path to plus the parameters\device // string. Add enough for a NULL at the end. // maxLength = RegistryPath->MaximumLength + tempKeyName.MaximumLength + 2; KeyName->Buffer = ExAllocatePool(NonPagedPool, maxLength); if (KeyName->Buffer == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(KeyName->Buffer, maxLength); // // Clone the Reg.Path. // KeyName->MaximumLength = (USHORT)maxLength; RtlCopyUnicodeString(KeyName, RegistryPath); // // Have a copy of the path to the services name. Add the rest of the keyname // to it. // status = RtlAppendUnicodeStringToString(KeyName, &tempKeyName); // // Free the buffer allocated above. // RtlFreeUnicodeString(&tempKeyName); return status; }