xref: /reactos/sdk/lib/ucrt/env/searchenv.cpp (revision 04e0dc4a)
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>
common_searchenv_s(_In_z_ Character const * const file_name,_In_z_ Character const * const environment_variable,_Out_writes_z_ (result_count)Character * const result_buffer,_In_ size_t const result_count)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 
_searchenv_s(char const * const file_name,char const * const environment_variable,char * const result_buffer,size_t const result_count)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 
_wsearchenv_s(wchar_t const * const file_name,wchar_t const * const environment_variable,wchar_t * const result_buffer,size_t const result_count)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 
_searchenv(char const * const file_name,char const * const environment_variable,char * const result_buffer)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 
_wsearchenv(wchar_t const * const file_name,wchar_t const * const environment_variable,wchar_t * const result_buffer)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