1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4//*****************************************************************************
5// SafeWrap.cpp
6//
7
8//
9// This file contains wrapper functions for Win32 API's that take SStrings
10// and use CLR-safe holders.
11//
12// See guidelines in SafeWrap.h for writing these APIs.
13//*****************************************************************************
14
15#include "stdafx.h" // Precompiled header key.
16#include "safewrap.h"
17#include "winwrap.h" // Header for macros and functions.
18#include "utilcode.h"
19#include "holder.h"
20#include "sstring.h"
21#include "ex.h"
22
23//-----------------------------------------------------------------------------
24// Get the current directory.
25// On success, returns true and sets 'Value' to unicode version of cur dir.
26// Throws on all failures. This should mainly be oom.
27//-----------------------------------------------------------------------------
28void ClrGetCurrentDirectory(SString & value)
29{
30 CONTRACTL
31 {
32 THROWS;
33 GC_NOTRIGGER;
34 }
35 CONTRACTL_END;
36
37 // Get size needed
38 DWORD len = WszGetCurrentDirectory(value);
39
40
41 // An actual API failure in GetCurrentDirectory failure should be very rare, so we'll throw on those.
42 if (len == 0)
43 {
44 ThrowLastError();
45 }
46}
47
48// Nothrowing wrapper.
49bool ClrGetCurrentDirectoryNoThrow(SString & value)
50{
51 CONTRACTL
52 {
53 NOTHROW;
54 GC_NOTRIGGER;
55 }
56 CONTRACTL_END;
57
58 bool fOk = true;
59 EX_TRY
60 {
61 ClrGetCurrentDirectory(value);
62 }
63 EX_CATCH
64 {
65 fOk = false;
66 }
67 EX_END_CATCH(SwallowAllExceptions)
68 return fOk;
69}
70//-----------------------------------------------------------------------------
71// Reads an environment variable into the given SString.
72// Returns true on success, false on failure (includes if the var does not exist).
73// May throw on oom.
74//-----------------------------------------------------------------------------
75bool ClrGetEnvironmentVariable(LPCSTR szEnvVarName, SString & value)
76{
77 CONTRACTL
78 {
79 THROWS;
80 GC_NOTRIGGER;
81
82 PRECONDITION(szEnvVarName != NULL);
83 }
84 CONTRACTL_END;
85
86 // First read it to get the needed length.
87 DWORD lenWithNull = GetEnvironmentVariableA(szEnvVarName, NULL, 0);
88 if (lenWithNull == 0)
89 {
90 return false;
91 }
92
93 // Now read it for content.
94 char * pCharBuf = value.OpenANSIBuffer(lenWithNull);
95 DWORD lenWithoutNull = GetEnvironmentVariableA(szEnvVarName, pCharBuf, lenWithNull);
96 value.CloseBuffer(lenWithoutNull);
97
98 if (lenWithoutNull != (lenWithNull - 1))
99 {
100 // Env var must have changed underneath us.
101 return false;
102 }
103 return true;
104}
105
106// Nothrowing wrapper.
107bool ClrGetEnvironmentVariableNoThrow(LPCSTR szEnvVarName, SString & value)
108{
109 CONTRACTL
110 {
111 NOTHROW;
112 GC_NOTRIGGER;
113 }
114 CONTRACTL_END;
115
116
117 bool fOk = false;
118 EX_TRY
119 {
120 fOk = ClrGetEnvironmentVariable(szEnvVarName, value);
121 }
122 EX_CATCH
123 {
124 fOk = false;
125 }
126 EX_END_CATCH(SwallowAllExceptions)
127 return fOk;
128}
129
130void ClrGetModuleFileName(HMODULE hModule, SString & value)
131{
132 CONTRACTL
133 {
134 THROWS;
135 GC_NOTRIGGER;
136 }
137 CONTRACTL_END;
138
139 WCHAR * pCharBuf = value.OpenUnicodeBuffer(_MAX_PATH);
140 DWORD numChars = GetModuleFileNameW(hModule, pCharBuf, _MAX_PATH);
141 value.CloseBuffer(numChars);
142}
143
144bool ClrGetModuleFileNameNoThrow(HMODULE hModule, SString & value)
145{
146 CONTRACTL
147 {
148 NOTHROW;
149 GC_NOTRIGGER;
150 }
151 CONTRACTL_END;
152
153 bool fOk = true;
154 EX_TRY
155 {
156 ClrGetModuleFileName(hModule, value);
157 }
158 EX_CATCH
159 {
160 fOk = false;
161 }
162 EX_END_CATCH(SwallowAllExceptions)
163 return fOk;
164}
165
166ClrDirectoryEnumerator::ClrDirectoryEnumerator(LPCWSTR pBaseDirectory, LPCWSTR pMask /*= W("*")*/)
167{
168 CONTRACTL
169 {
170 THROWS;
171 GC_NOTRIGGER;
172 }
173 CONTRACTL_END;
174
175 StackSString strMask(pBaseDirectory);
176 SString s(SString::Literal, DIRECTORY_SEPARATOR_STR_W);
177 if (!strMask.EndsWith(s))
178 {
179 strMask.Append(s);
180 }
181 strMask.Append(pMask);
182 dirHandle = WszFindFirstFile(strMask, &data);
183
184 if (dirHandle == INVALID_HANDLE_VALUE)
185 {
186 DWORD dwLastError = GetLastError();
187
188 // We either ran out of files, or didnt encounter even a single file matching the
189 // search mask. If it is neither of these conditions, then convert the error to an exception
190 // and raise it.
191 if ((dwLastError != ERROR_FILE_NOT_FOUND) && (dwLastError != ERROR_NO_MORE_FILES))
192 ThrowLastError();
193 }
194
195 fFindNext = FALSE;
196}
197
198bool ClrDirectoryEnumerator::Next()
199{
200 CONTRACTL
201 {
202 THROWS;
203 GC_NOTRIGGER;
204 }
205 CONTRACTL_END;
206
207 if (dirHandle == INVALID_HANDLE_VALUE)
208 return FALSE;
209
210 for (;;)
211 {
212 if (fFindNext)
213 {
214 if (!WszFindNextFile(dirHandle, &data))
215 {
216 if (GetLastError() != ERROR_NO_MORE_FILES)
217 ThrowLastError();
218
219 return FALSE;
220 }
221 }
222 else
223 {
224 fFindNext = TRUE;
225 }
226
227 // Skip junk
228 if (wcscmp(data.cFileName, W(".")) != 0 && wcscmp(data.cFileName, W("..")) != 0)
229 return TRUE;
230 }
231}
232
233DWORD ClrReportEvent(
234 LPCWSTR pEventSource,
235 WORD wType,
236 WORD wCategory,
237 DWORD dwEventID,
238 PSID lpUserSid,
239 WORD wNumStrings,
240 LPCWSTR *lpStrings,
241 DWORD dwDataSize /*=0*/,
242 LPVOID lpRawData /*=NULL*/)
243{
244 CONTRACTL
245 {
246 NOTHROW;
247 GC_NOTRIGGER;
248 }
249 CONTRACTL_END;
250
251#ifndef FEATURE_PAL
252 HANDLE h = ::RegisterEventSourceW(
253 NULL, // uses local computer
254 pEventSource);
255 if (h == NULL)
256 {
257 // Return the error code to the caller so that
258 // appropriate asserts/logging can be done
259 // incase of errors like event log being full
260 return GetLastError();
261 }
262
263 // Every event id should have matching description in dlls\shim\eventmsg.mc. This allows
264 // event view to know how to display message.
265 _ASSERTE (dwEventID != 0);
266
267 // Attempt to report the event to the event log. Note that if the operation fails
268 // (for example because of low memory conditions) it isn't fatal so we can safely ignore
269 // the return code from ReportEventW.
270 BOOL ret = ::ReportEventW(
271 h, // event log handle
272 wType,
273 wCategory,
274 dwEventID,
275 lpUserSid,
276 wNumStrings,
277 dwDataSize,
278 lpStrings,
279 lpRawData);
280
281 DWORD dwRetStatus = GetLastError();
282
283 ::DeregisterEventSource(h);
284
285 return (ret == TRUE)?ERROR_SUCCESS:dwRetStatus;
286#else // FEATURE_PAL
287 // UNIXTODO: Report the event somewhere?
288 return ERROR_SUCCESS;
289#endif // FEATURE_PAL
290}
291
292// Returns ERROR_SUCCESS if succeessful in reporting to event log, or
293// Windows error code to indicate the specific error.
294DWORD ClrReportEvent(
295 LPCWSTR pEventSource,
296 WORD wType,
297 WORD wCategory,
298 DWORD dwEventID,
299 PSID lpUserSid,
300 LPCWSTR pMessage)
301{
302 return ClrReportEvent(pEventSource, wType, wCategory, dwEventID, lpUserSid, 1, &pMessage);
303}
304
305#ifndef FEATURE_PAL
306// Read a REG_SZ (null-terminated string) value from the registry. Throws.
307//
308// Arguments:
309// hKey - key to registry hive.
310// szValueName - null-terminated string for value name to lookup.
311// If this is empty, this gets the (default) value in the registry hive.
312// value - out parameter to hold registry value string contents.
313//
314// Returns:
315// value is set on success. Throws on any failure, including if the value doesn't exist
316// or if the value exists but is not a REG_SZ.
317//
318// Notes:
319// REG_SZ is a single null-terminated string in the registry.
320// This is only support on Windows because the registry is windows specific.
321void ClrRegReadString(HKEY hKey, const SString & szValueName, SString & value)
322{
323 CONTRACTL
324 {
325 THROWS;
326 GC_NOTRIGGER;
327 }
328 CONTRACTL_END;
329
330 DWORD type;
331 DWORD numBytesData;
332
333 // Preemptively clear the string such that it's empty in any failure case.
334 value.Clear();
335
336 //
337 // Step 1: First call to find size of buffer and ensure data type is correct
338 //
339 LONG ret = WszRegQueryValueEx(
340 hKey,
341 szValueName.GetUnicode(), // NULL or "\0" represents the (default) key.
342 NULL, // reserved
343 &type, // should be REG_SZ
344 NULL, // not requesting data yet
345 &numBytesData
346 );
347
348 if (ret != ERROR_SUCCESS)
349 {
350 ThrowWin32(ret);
351 }
352
353 if (type != REG_SZ)
354 {
355 // The registry value is not a string.
356 ThrowHR(E_INVALIDARG);
357 }
358
359 // REG_SZ includes the null terminator.
360 DWORD numCharsIncludingNull = numBytesData / sizeof(WCHAR);
361
362 //
363 // Step 2: Allocate buffer to hold final result
364 //
365 WCHAR * pData = value.OpenUnicodeBuffer(numCharsIncludingNull);
366 DWORD numBytesData2 = numBytesData;
367
368
369 //
370 // Step 3: Requery to get actual contents
371 //
372 ret = WszRegQueryValueEx(
373 hKey,
374 szValueName.GetUnicode(),
375 NULL, // reserved
376 &type, // should still be REG_SZ
377 (LPBYTE) pData,
378 &numBytesData2
379 );
380
381 // This check should only fail if the registry was changed inbetween the first query
382 // and the second. In practice, that should never actually happen.
383 if ((numBytesData2 != numBytesData) || (type != REG_SZ))
384 {
385 // On error, leave string empty.
386 value.CloseBuffer(0);
387
388 ThrowHR(E_FAIL);
389 }
390
391 if (ret != ERROR_SUCCESS)
392 {
393 // On error, leave string empty.
394 value.CloseBuffer(0);
395 ThrowWin32(ret);
396 }
397
398
399 //
400 // Step 4: Close the string buffer
401 //
402 COUNT_T numCharsNoNull = numCharsIncludingNull - 1;
403 value.CloseBuffer(numCharsNoNull);
404}
405#endif // FEATURE_PAL
406