1 // 2 // searchenv.cpp 3 // 4 // Copyright (c) Microsoft Corporation. All rights reserved. 5 // 6 // Defines the searchenv() family of functions, which search for a file in the 7 // paths contained in an environment variable (like %PATH%). 8 // 9 #include <direct.h> 10 #include <corecrt_internal_traits.h> 11 #include <io.h> 12 #include <stdlib.h> 13 #include <string.h> 14 15 #pragma warning(disable:__WARNING_POSTCONDITION_NULLTERMINATION_VIOLATION) // 26036 Prefast doesn't understand perfect forwarding so annotations are lost. 16 #pragma warning(disable:__WARNING_MISSING_ZERO_TERMINATION2) // 6054 Prefast doesn't understand perfect forwarding so annotations are lost. 17 18 // These functions search for a file in the paths in an environment variable. 19 // The environment variable named 'environment_variable' is retrieved from the 20 // environment. It is expected to contain a semicolon-delimited sequence of 21 // directories, similar to %PATH%. The file name is concatenated onto the end 22 // of each directory string in sequence and its existence is tested for. When 23 // a file is found, the search stops and the buffer is filled with the full path 24 // to the file. If the file is not found for whatever reason, a zero-length 25 // string is placed in the buffer and an error code is returned. 26 template <typename Character> 27 static errno_t __cdecl common_searchenv_s( 28 _In_z_ Character const* const file_name, 29 _In_z_ Character const* const environment_variable, 30 _Out_writes_z_(result_count) Character* const result_buffer, 31 _In_ size_t const result_count 32 ) throw() 33 { 34 typedef __crt_char_traits<Character> traits; 35 36 _VALIDATE_RETURN_ERRCODE(result_buffer != nullptr, EINVAL); 37 _VALIDATE_RETURN_ERRCODE(result_count > 0, EINVAL); 38 if (!file_name) 39 { 40 result_buffer[0] = '\0'; 41 _VALIDATE_RETURN_ERRCODE(file_name != nullptr, EINVAL); 42 } 43 44 // Special case: If the file name is an empty string, we'll just return an 45 // empty result_buffer and set errno: 46 if (file_name[0] == '\0') 47 { 48 result_buffer[0] = '\0'; 49 return errno = ENOENT; 50 } 51 52 errno_t saved_errno = errno; 53 int const access_result = traits::taccess_s(file_name, 0); 54 errno = saved_errno; 55 56 // If the file name exists, convert it to a fully qualified result_buffer name: 57 if (access_result == 0) 58 { 59 if (traits::tfullpath(result_buffer, file_name, result_count) == nullptr) 60 { 61 result_buffer[0] = '\0'; 62 return errno; // fullpath will set errno 63 } 64 65 return 0; 66 } 67 68 Character* path_string = nullptr; 69 if (_ERRCHECK_EINVAL(traits::tdupenv_s_crt(&path_string, nullptr, environment_variable)) != 0 || path_string == nullptr) 70 { 71 result_buffer[0] = '\0'; 72 return errno = ENOENT; // The environment variable doesn't exist 73 } 74 75 __crt_unique_heap_ptr<Character> const path_string_cleanup(path_string); 76 77 size_t const file_name_length = traits::tcslen(file_name); 78 79 size_t const local_path_count = _MAX_PATH + 4; 80 Character local_path_buffer[local_path_count]; 81 82 size_t path_count = local_path_count; 83 Character* path_buffer = local_path_buffer; 84 if (file_name_length >= result_count) 85 { 86 // The local buffer is not large enough; dynamically allocate a new 87 // buffer. We add two to the size to account for a trailing slash 88 // that we may need to add and for the null terminator: 89 path_count = traits::tcslen(path_string) + file_name_length + 2; 90 path_buffer = _calloc_crt_t(Character, path_count).detach(); // We'll retake ownership below 91 if (!path_buffer) 92 { 93 result_buffer[0] = '\0'; 94 return errno = ENOMEM; 95 } 96 } 97 98 __crt_unique_heap_ptr<Character> path_buffer_cleanup(path_buffer == local_path_buffer 99 ? nullptr 100 : path_buffer); 101 102 saved_errno = errno; 103 while (path_string) 104 { 105 Character* const previous_path_string = path_string; 106 path_string = traits::tgetpath(path_string, path_buffer, path_count - file_name_length - 1); 107 if (!path_string && path_buffer == local_path_buffer && errno == ERANGE) 108 { 109 // If the getpath operation failed because the buffer was not large 110 // enough, try allocating a larger buffer: 111 size_t const required_count = traits::tcslen(previous_path_string) + file_name_length + 2; 112 113 path_buffer_cleanup = _calloc_crt_t(Character, required_count); 114 if (!path_buffer_cleanup) 115 { 116 result_buffer[0] = '\0'; 117 return errno = ENOMEM; 118 } 119 120 path_count = required_count; 121 path_buffer = path_buffer_cleanup.get(); 122 123 path_string = traits::tgetpath(previous_path_string, path_buffer, path_count - file_name_length); 124 } 125 126 if (!path_string || path_buffer[0] == '\0') 127 { 128 result_buffer[0] = '\0'; 129 return errno = ENOENT; 130 } 131 132 // The result_buffer now holds a non-empty path name from path_string 133 // and we know that the buffer is large enough to hold the concatenation 134 // of the path with the file name (if not, the call to getpath would 135 // have failed. So, we concatenate the path and file names: 136 size_t path_length = traits::tcslen(path_buffer); 137 Character* path_it = path_buffer + path_length; 138 139 // Add a trailing '\' if one is required: 140 Character const last_character = *(path_it - 1); 141 if (last_character != '/' && last_character != '\\' && last_character != ':') 142 { 143 *path_it++ = '\\'; 144 ++path_length; 145 } 146 147 // The path_it now points to the character following the trailing '\', 148 // '/', or ':'; this is where we copy the file name: 149 _ERRCHECK(traits::tcscpy_s(path_it, path_count - path_length, file_name)); 150 151 // If we can't access the file at this path, it isn't a match: 152 if (traits::taccess_s(path_buffer, 0) != 0) 153 continue; 154 155 // Otherwise, we can access the file, and we copy the full path into the 156 // caller-provided buffer: 157 if (path_length + file_name_length + 1 > result_count) 158 { 159 result_buffer[0] = '\0'; 160 return errno = ERANGE; 161 } 162 163 errno = saved_errno; 164 165 _ERRCHECK(traits::tcscpy_s(result_buffer, result_count, path_buffer)); 166 return 0; 167 } 168 169 // If we get here, we must have tried every path in the environment and not 170 // found the file name: 171 result_buffer[0] = '\0'; 172 return errno = ENOENT; 173 } 174 175 176 177 extern "C" errno_t __cdecl _searchenv_s( 178 char const* const file_name, 179 char const* const environment_variable, 180 char* const result_buffer, 181 size_t const result_count 182 ) 183 { 184 return common_searchenv_s(file_name, environment_variable, result_buffer, result_count); 185 } 186 187 extern "C" errno_t __cdecl _wsearchenv_s( 188 wchar_t const* const file_name, 189 wchar_t const* const environment_variable, 190 wchar_t* const result_buffer, 191 size_t const result_count 192 ) 193 { 194 return common_searchenv_s(file_name, environment_variable, result_buffer, result_count); 195 } 196 197 198 199 extern "C" void __cdecl _searchenv( 200 char const* const file_name, 201 char const* const environment_variable, 202 char* const result_buffer 203 ) 204 { 205 common_searchenv_s(file_name, environment_variable, result_buffer, _MAX_PATH); 206 } 207 208 extern "C" void __cdecl _wsearchenv( 209 wchar_t const* const file_name, 210 wchar_t const* const environment_variable, 211 wchar_t* const result_buffer 212 ) 213 { 214 common_searchenv_s(file_name, environment_variable, result_buffer, _MAX_PATH); 215 } 216