/*++ Copyright (c) 1990 Microsoft Corporation Module Name: environ.c Abstract: Environment Variable support Author: Steven R. Wood (stevewo) 30-Jan-1991 Revision History: --*/ #include "ntrtlp.h" #include "zwapi.h" #include "nturtl.h" #include "string.h" #include "ntrtlpath.h" #if defined(ALLOC_PRAGMA) && defined(NTOS_KERNEL_RUNTIME) #pragma alloc_text(INIT,RtlCreateEnvironment ) #pragma alloc_text(INIT,RtlDestroyEnvironment ) #pragma alloc_text(INIT,RtlSetCurrentEnvironment ) #pragma alloc_text(INIT,RtlQueryEnvironmentVariable_U ) #pragma alloc_text(INIT,RtlSetEnvironmentVariable ) #endif BOOLEAN RtlpEnvironCacheValid; NTSTATUS RtlCreateEnvironment( IN BOOLEAN CloneCurrentEnvironment OPTIONAL, OUT PVOID *Environment ) { NTSTATUS Status; MEMORY_BASIC_INFORMATION MemoryInformation; PVOID pNew, pOld; // // If not cloning a copy of the current process's environment variable // block, just allocate a block of committed memory and return its // address. // pNew = NULL; if (!CloneCurrentEnvironment) { createEmptyEnvironment: MemoryInformation.RegionSize = 1; Status = ZwAllocateVirtualMemory( NtCurrentProcess(), &pNew, 0, &MemoryInformation.RegionSize, MEM_COMMIT, PAGE_READWRITE ); if (NT_SUCCESS( Status )) { *Environment = pNew; } return( Status ); } // // Acquire the Peb Lock for the duration while we munge the environment // variable storage block. // RtlAcquirePebLock(); // // Capture the pointer to the current process's environment variable // block and initialize the new pointer to null for our finally clause. // pOld = NtCurrentPeb()->ProcessParameters->Environment; if (pOld == NULL) { RtlReleasePebLock(); goto createEmptyEnvironment; } try { try { // // Query the current size of the current process's environment // variable block. Return status if failure. // Status = ZwQueryVirtualMemory( NtCurrentProcess(), pOld, MemoryBasicInformation, &MemoryInformation, sizeof( MemoryInformation ), NULL ); if (!NT_SUCCESS( Status )) { leave; } // // Allocate memory to contain a copy of the current process's // environment variable block. Return status if failure. // Status = ZwAllocateVirtualMemory( NtCurrentProcess(), &pNew, 0, &MemoryInformation.RegionSize, MEM_COMMIT, PAGE_READWRITE ); if (!NT_SUCCESS( Status )) { leave; } // // Copy the current process's environment to the allocated memory // and return a pointer to the copy. // RtlCopyMemory( pNew, pOld, MemoryInformation.RegionSize ); *Environment = pNew; } except (EXCEPTION_EXECUTE_HANDLER) { Status = STATUS_ACCESS_VIOLATION; } } finally { if (Status == STATUS_ACCESS_VIOLATION) { if (pNew != NULL) { ZwFreeVirtualMemory( NtCurrentProcess(), &pNew, &MemoryInformation.RegionSize, MEM_RELEASE ); } } RtlReleasePebLock(); } return( Status ); } NTSTATUS RtlDestroyEnvironment( IN PVOID Environment ) { NTSTATUS Status; SIZE_T RegionSize; // // Free the specified environment variable block. // RtlpEnvironCacheValid = FALSE; RegionSize = 0; Status = ZwFreeVirtualMemory( NtCurrentProcess(), &Environment, &RegionSize, MEM_RELEASE ); // // Return status. // return( Status ); } NTSTATUS RtlSetCurrentEnvironment( IN PVOID Environment, OUT PVOID *PreviousEnvironment OPTIONAL ) { NTSTATUS Status; PVOID pOld; // // Acquire the Peb Lock for the duration while we munge the environment // variable storage block. // RtlpEnvironCacheValid = FALSE; RtlAcquirePebLock(); Status = STATUS_SUCCESS; try { // // Capture current process's environment variable block pointer to // return to caller or destroy. // pOld = NtCurrentPeb()->ProcessParameters->Environment; // // Change current process's environment variable block pointer to // point to the passed block. // NtCurrentPeb()->ProcessParameters->Environment = Environment; // // If caller requested it, return the pointer to the previous // process environment variable block and set the local variable // to NULL so we dont destroy it below. // if (ARGUMENT_PRESENT( PreviousEnvironment )) { *PreviousEnvironment = pOld; pOld = NULL; } } except (EXCEPTION_EXECUTE_HANDLER) { Status = STATUS_ACCESS_VIOLATION; pOld = NULL; } // // Release the Peb Lock // RtlReleasePebLock(); // // If old environment not returned to caller, destroy it. // if (pOld != NULL) { RtlDestroyEnvironment( pOld ); } // // Return status // return( Status ); } UNICODE_STRING RtlpEnvironCacheName; UNICODE_STRING RtlpEnvironCacheValue; NTSTATUS RtlQueryEnvironmentVariable_U( IN PVOID Environment OPTIONAL, IN PUNICODE_STRING Name, IN OUT PUNICODE_STRING Value ) { NTSTATUS Status; UNICODE_STRING CurrentName; UNICODE_STRING CurrentValue; PWSTR p; PPEB Peb; BOOLEAN PebLockLocked = FALSE; Status = STATUS_VARIABLE_NOT_FOUND; Peb = NtCurrentPeb(); try { if (ARGUMENT_PRESENT( Environment )) { p = Environment; if (*p == UNICODE_NULL) { leave; } } else { // // Acquire the Peb Lock for the duration while we munge the // environment variable storage block. // RtlAcquirePebLock(); PebLockLocked = TRUE; // // Capture the pointer to the current process's environment variable // block. // p = Peb->ProcessParameters->Environment; } if ( RtlpEnvironCacheValid && p == Peb->ProcessParameters->Environment ) { if (RtlEqualUnicodeString( Name, &RtlpEnvironCacheName, TRUE )) { // // Names are equal. Always return the length of the // value string, excluding the terminating null. If // there is room in the caller's buffer, return a copy // of the value string and success status. Otherwise // return an error status. In the latter case, the caller // can examine the length field of their value string // so they can determine much memory is needed. // Value->Length = RtlpEnvironCacheValue.Length; if (Value->MaximumLength >= RtlpEnvironCacheValue.Length) { RtlCopyMemory( Value->Buffer, RtlpEnvironCacheValue.Buffer, RtlpEnvironCacheValue.Length ); // // Null terminate returned string if there is room. // if (Value->MaximumLength > RtlpEnvironCacheValue.Length) { Value->Buffer[ RtlpEnvironCacheValue.Length/sizeof(WCHAR) ] = L'\0'; } Status = STATUS_SUCCESS; } else { Status = STATUS_BUFFER_TOO_SMALL; } leave; } } // // The environment variable block consists of zero or more null // terminated UNICODE strings. Each string is of the form: // // name=value // // where the null termination is after the value. // if (p != NULL) while (*p) { // // Determine the size of the name and value portions of // the current string of the environment variable block. // CurrentName.Buffer = p; CurrentName.Length = 0; CurrentName.MaximumLength = 0; while (*p) { // // If we see an equal sign, then compute the size of // the name portion and scan for the end of the value. // if (*p == L'=' && p != CurrentName.Buffer) { CurrentName.Length = (USHORT)(p - CurrentName.Buffer)*sizeof(WCHAR); CurrentName.MaximumLength = (USHORT)(CurrentName.Length+sizeof(WCHAR)); CurrentValue.Buffer = ++p; while(*p) { p++; } CurrentValue.Length = (USHORT)(p - CurrentValue.Buffer)*sizeof(WCHAR); CurrentValue.MaximumLength = (USHORT)(CurrentValue.Length+sizeof(WCHAR)); // // At this point we have the length of both the name // and value portions, so exit the loop so we can // do the compare. // break; } else { p++; } } // // Skip over the terminating null character for this name=value // pair in preparation for the next iteration of the loop. // p++; // // Compare the current name with the one requested, ignore // case. // if (RtlEqualUnicodeString( Name, &CurrentName, TRUE )) { // // Names are equal. Always return the length of the // value string, excluding the terminating null. If // there is room in the caller's buffer, return a copy // of the value string and success status. Otherwise // return an error status. In the latter case, the caller // can examine the length field of their value string // so they can determine much memory is needed. // Value->Length = CurrentValue.Length; if (Value->MaximumLength >= CurrentValue.Length) { RtlCopyMemory( Value->Buffer, CurrentValue.Buffer, CurrentValue.Length ); // // Null terminate returned string if there is room. // if (Value->MaximumLength > CurrentValue.Length) { Value->Buffer[ CurrentValue.Length/sizeof(WCHAR) ] = L'\0'; } if ( !Environment || Environment == Peb->ProcessParameters->Environment) { RtlpEnvironCacheValid = TRUE; RtlpEnvironCacheName = CurrentName; RtlpEnvironCacheValue = CurrentValue; } Status = STATUS_SUCCESS; } else { Status = STATUS_BUFFER_TOO_SMALL; } break; } } // If it's not in the real env block, let's see if it's a pseudo environment variable if (Status == STATUS_VARIABLE_NOT_FOUND) { static const UNICODE_STRING CurrentWorkingDirectoryPseudoVariable = RTL_CONSTANT_STRING(L"__CD__"); static const UNICODE_STRING ApplicationDirectoryPseudoVariable = RTL_CONSTANT_STRING(L"__APPDIR__"); if (RtlEqualUnicodeString(Name, &CurrentWorkingDirectoryPseudoVariable, TRUE)) { // Get the PEB lock if we don't already have it. if (!PebLockLocked) { RtlAcquirePebLock(); PebLockLocked = TRUE; } // get cdw here... CurrentValue = NtCurrentPeb()->ProcessParameters->CurrentDirectory.DosPath; Status = STATUS_SUCCESS; } else if (RtlEqualUnicodeString(Name, &ApplicationDirectoryPseudoVariable, TRUE)) { USHORT PrefixLength = 0; if (!PebLockLocked) { RtlAcquirePebLock(); PebLockLocked = TRUE; } // get appdir here CurrentValue = NtCurrentPeb()->ProcessParameters->ImagePathName; Status = RtlFindCharInUnicodeString( RTL_FIND_CHAR_IN_UNICODE_STRING_START_AT_END, &CurrentValue, &RtlDosPathSeperatorsString, &PrefixLength); if (NT_SUCCESS(Status)) { CurrentValue.Length = PrefixLength + sizeof(WCHAR); } else if (Status == STATUS_NOT_FOUND) { // Use the whole thing; just translate the status to successs. Status = STATUS_SUCCESS; } } if (NT_SUCCESS(Status)) { Value->Length = CurrentValue.Length; if (Value->MaximumLength >= CurrentValue.Length) { RtlCopyMemory(Value->Buffer, CurrentValue.Buffer, CurrentValue.Length); // // Null terminate returned string if there is room. // if (Value->MaximumLength > CurrentValue.Length) Value->Buffer[ CurrentValue.Length/sizeof(WCHAR) ] = L'\0'; } } } } except (EXCEPTION_EXECUTE_HANDLER) { Status = STATUS_ACCESS_VIOLATION; } // // Release the Peb lock. // if (PebLockLocked) RtlReleasePebLock(); // // Return status. // return Status; } NTSTATUS RtlSetEnvironmentVariable( IN OUT PVOID *Environment OPTIONAL, IN PUNICODE_STRING Name, IN PUNICODE_STRING Value OPTIONAL ) { NTSTATUS Status; MEMORY_BASIC_INFORMATION MemoryInformation; UNICODE_STRING CurrentName; UNICODE_STRING CurrentValue; PVOID pOld, pNew; ULONG n, Size; SIZE_T NewSize; LONG CompareResult; PWSTR p, pStart, pEnd; PWSTR InsertionPoint; // // Validate passed in name and reject if zero length or anything but the first // character is an equal sign. // n = Name->Length / sizeof( WCHAR ); if (n == 0) { return STATUS_INVALID_PARAMETER; } try { p = Name->Buffer; while (--n) { if (*++p == L'=') { return STATUS_INVALID_PARAMETER; } } } except (EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode(); } RtlpEnvironCacheValid = FALSE; Status = STATUS_VARIABLE_NOT_FOUND; if (ARGUMENT_PRESENT( Environment )) { pOld = *Environment; } else { // // Acquire the Peb Lock for the duration while we munge the // environment variable storage block. // RtlAcquirePebLock(); // // Capture the pointer to the current process's environment variable // block. // pOld = NtCurrentPeb()->ProcessParameters->Environment; } pNew = NULL; InsertionPoint = NULL; try { try { // // The environment variable block consists of zero or more null // terminated UNICODE strings. Each string is of the form: // // name=value // // where the null termination is after the value. // p = pOld; pEnd = NULL; if (p != NULL) while (*p) { // // Determine the size of the name and value portions of // the current string of the environment variable block. // CurrentName.Buffer = p; CurrentName.Length = 0; CurrentName.MaximumLength = 0; while (*p) { // // If we see an equal sign, then compute the size of // the name portion and scan for the end of the value. // if (*p == L'=' && p != CurrentName.Buffer) { CurrentName.Length = (USHORT)(p - CurrentName.Buffer) * sizeof(WCHAR); CurrentName.MaximumLength = (USHORT)(CurrentName.Length+sizeof(WCHAR)); CurrentValue.Buffer = ++p; while(*p) { p++; } CurrentValue.Length = (USHORT)(p - CurrentValue.Buffer) * sizeof(WCHAR); CurrentValue.MaximumLength = (USHORT)(CurrentValue.Length+sizeof(WCHAR)); // // At this point we have the length of both the name // and value portions, so exit the loop so we can // do the compare. // break; } else { p++; } } // // Skip over the terminating null character for this name=value // pair in preparation for the next iteration of the loop. // p++; // // Compare the current name with the one requested, ignore // case. // if (!(CompareResult = RtlCompareUnicodeString( Name, &CurrentName, TRUE ))) { // // Names are equal. Now find the end of the current // environment variable block. // pEnd = p; while (*pEnd) { while (*pEnd++) { } } pEnd++; if (!ARGUMENT_PRESENT( Value )) { // // If the caller did not specify a new value, then delete // the entire name=value pair by copying up the remainder // of the environment variable block. // RtlMoveMemory( CurrentName.Buffer, p, (ULONG) ((pEnd - p)*sizeof(WCHAR)) ); Status = STATUS_SUCCESS; } else if (Value->Length <= CurrentValue.Length) { // // New value is smaller, so copy new value, then null // terminate it, and then move up the remainder of the // variable block so it is immediately after the new // null terminated value. // pStart = CurrentValue.Buffer; RtlMoveMemory( pStart, Value->Buffer, Value->Length ); pStart += Value->Length/sizeof(WCHAR); *pStart++ = L'\0'; RtlMoveMemory( pStart, p,(ULONG)((pEnd - p)*sizeof(WCHAR)) ); Status = STATUS_SUCCESS; } else { // // New value is larger, so query the current size of the // environment variable block. Return status if failure. // Status = ZwQueryVirtualMemory( NtCurrentProcess(), pOld, MemoryBasicInformation, &MemoryInformation, sizeof( MemoryInformation ), NULL ); if (!NT_SUCCESS( Status )) { leave; } // // See if there is room for new, larger value. If not // allocate a new copy of the environment variable // block. // NewSize = (pEnd - (PWSTR)pOld)*sizeof(WCHAR) + Value->Length - CurrentValue.Length; if (NewSize >= MemoryInformation.RegionSize) { // // Allocate memory to contain a copy of the current // process's environment variable block. Return // status if failure. // Status = ZwAllocateVirtualMemory( NtCurrentProcess(), &pNew, 0, &NewSize, MEM_COMMIT, PAGE_READWRITE ); if (!NT_SUCCESS( Status )) { leave; } // // Copy the current process's environment to the allocated memory // inserting the new value as we do the copy. // Size = (ULONG) (CurrentValue.Buffer - (PWSTR)pOld); RtlMoveMemory( pNew, pOld, Size*sizeof(WCHAR) ); pStart = (PWSTR)pNew + Size; RtlMoveMemory( pStart, Value->Buffer, Value->Length ); pStart += Value->Length/sizeof(WCHAR); *pStart++ = L'\0'; RtlMoveMemory( pStart, p,(ULONG)((pEnd - p)*sizeof(WCHAR))); if (ARGUMENT_PRESENT( Environment )) *Environment = pNew; else { NtCurrentPeb()->ProcessParameters->Environment = pNew; NtCurrentPeb()->EnvironmentUpdateCount += 1; } ZwFreeVirtualMemory( NtCurrentProcess(), &pOld, &MemoryInformation.RegionSize, MEM_RELEASE ); pNew = pOld; } else { pStart = CurrentValue.Buffer + Value->Length/sizeof(WCHAR) + 1; RtlMoveMemory( pStart, p,(ULONG)((pEnd - p)*sizeof(WCHAR))); *--pStart = L'\0'; RtlMoveMemory( pStart - Value->Length/sizeof(WCHAR), Value->Buffer, Value->Length ); } } break; } else if (CompareResult < 0) { // // Requested name is less than the current name. Save this // spot in case the variable is not in a sorted position. // The insertion point for the new variable is before the // variable just examined. // if (InsertionPoint == NULL) { InsertionPoint = CurrentName.Buffer; } } } // // If we found an insertion point, reset the string // pointer back to it. // if (InsertionPoint != NULL) { p = InsertionPoint; } // // If variable name not found and a new value parameter was specified // then insert the new variable name and its value at the appropriate // place in the environment variable block (i.e. where p points to). // if (pEnd == NULL && ARGUMENT_PRESENT( Value )) { if (p != NULL) { // // Name not found. Now find the end of the current // environment variable block. // pEnd = p; while (*pEnd) { while (*pEnd++) { } } pEnd++; // // New value is present, so query the current size of the // environment variable block. Return status if failure. // Status = ZwQueryVirtualMemory( NtCurrentProcess(), pOld, MemoryBasicInformation, &MemoryInformation, sizeof( MemoryInformation ), NULL ); if (!NT_SUCCESS( Status )) { leave; } // // See if there is room for new, larger value. If not // allocate a new copy of the environment variable // block. // NewSize = (pEnd - (PWSTR)pOld) * sizeof(WCHAR) + Name->Length + sizeof(WCHAR) + Value->Length + sizeof(WCHAR); } else { NewSize = Name->Length + sizeof(WCHAR) + Value->Length + sizeof(WCHAR); MemoryInformation.RegionSize = 0; } if (NewSize >= MemoryInformation.RegionSize) { // // Allocate memory to contain a copy of the current // process's environment variable block. Return // status if failure. // Status = ZwAllocateVirtualMemory( NtCurrentProcess(), &pNew, 0, &NewSize, MEM_COMMIT, PAGE_READWRITE ); if (!NT_SUCCESS( Status )) { leave; } // // Copy the current process's environment to the allocated memory // inserting the new value as we do the copy. // if (p != NULL) { Size = (ULONG)(p - (PWSTR)pOld); RtlMoveMemory( pNew, pOld, Size*sizeof(WCHAR) ); } else { Size = 0; } pStart = (PWSTR)pNew + Size; RtlMoveMemory( pStart, Name->Buffer, Name->Length ); pStart += Name->Length/sizeof(WCHAR); *pStart++ = L'='; RtlMoveMemory( pStart, Value->Buffer, Value->Length ); pStart += Value->Length/sizeof(WCHAR); *pStart++ = L'\0'; if (p != NULL) { RtlMoveMemory( pStart, p,(ULONG)((pEnd - p)*sizeof(WCHAR)) ); } if (ARGUMENT_PRESENT( Environment )) { *Environment = pNew; } else { NtCurrentPeb()->ProcessParameters->Environment = pNew; NtCurrentPeb()->EnvironmentUpdateCount += 1; } ZwFreeVirtualMemory( NtCurrentProcess(), &pOld, &MemoryInformation.RegionSize, MEM_RELEASE ); } else { pStart = p + Name->Length/sizeof(WCHAR) + 1 + Value->Length/sizeof(WCHAR) + 1; RtlMoveMemory( pStart, p,(ULONG)((pEnd - p)*sizeof(WCHAR)) ); RtlMoveMemory( p, Name->Buffer, Name->Length ); p += Name->Length/sizeof(WCHAR); *p++ = L'='; RtlMoveMemory( p, Value->Buffer, Value->Length ); p += Value->Length/sizeof(WCHAR); *p++ = L'\0'; } } } except(EXCEPTION_EXECUTE_HANDLER) { // // If abnormally terminating, assume access violation. // Status = STATUS_ACCESS_VIOLATION; } } finally { // // Release the Peb lock. // if (!ARGUMENT_PRESENT( Environment )) { RtlReleasePebLock(); } } // // Return status. // return( Status ); }