main
cpp 1,456 lines 38 KB
Raw
1 // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 #include "precomp.h"
4
5
6 // Exit macros
7 #define PathExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
8 #define PathExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
9 #define PathExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
10 #define PathExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
11 #define PathExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
12 #define PathExitWithRootFailure(x, e, s, ...) ExitWithRootFailureSource(DUTIL_SOURCE_PATHUTIL, x, e, s, __VA_ARGS__)
13 #define PathExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_PATHUTIL, x, s, __VA_ARGS__)
14 #define PathExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_PATHUTIL, p, x, e, s, __VA_ARGS__)
15 #define PathExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_PATHUTIL, p, x, s, __VA_ARGS__)
16 #define PathExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_PATHUTIL, p, x, e, s, __VA_ARGS__)
17 #define PathExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_PATHUTIL, p, x, s, __VA_ARGS__)
18 #define PathExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_PATHUTIL, e, x, s, __VA_ARGS__)
19 #define PathExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_PATHUTIL, g, x, s, __VA_ARGS__)
20
21 #define PATH_GOOD_ENOUGH 64
22
23 typedef DWORD(APIENTRY* PFN_GETTEMPPATH2W)(
24 __in DWORD BufferLength,
25 __out LPWSTR Buffer
26 );
27
28 static BOOL IsPathSeparatorChar(
29 __in WCHAR wc
30 );
31 static BOOL IsValidDriveChar(
32 __in WCHAR wc
33 );
34
35
36 DAPI_(LPWSTR) PathFile(
37 __in_z LPCWSTR wzPath
38 )
39 {
40 if (!wzPath)
41 {
42 return NULL;
43 }
44
45 LPWSTR wzFile = const_cast<LPWSTR>(wzPath);
46 for (LPWSTR wz = wzFile; *wz; ++wz)
47 {
48 // valid delineators
49 // \ => Windows path
50 // / => unix and URL path
51 // : => relative path from mapped root
52 if (IsPathSeparatorChar(*wz) || (L':' == *wz && wz == wzPath + 1))
53 {
54 wzFile = wz + 1;
55 }
56 }
57
58 return wzFile;
59 }
60
61
62 DAPI_(LPCWSTR) PathExtension(
63 __in_z LPCWSTR wzPath
64 )
65 {
66 if (!wzPath)
67 {
68 return NULL;
69 }
70
71 // Find the last dot in the last thing that could be a file.
72 LPCWSTR wzExtension = NULL;
73 for (LPCWSTR wz = wzPath; *wz; ++wz)
74 {
75 if (IsPathSeparatorChar(*wz) || L':' == *wz)
76 {
77 wzExtension = NULL;
78 }
79 else if (L'.' == *wz)
80 {
81 wzExtension = wz;
82 }
83 }
84
85 return wzExtension;
86 }
87
88
89 DAPI_(HRESULT) PathGetDirectory(
90 __in_z LPCWSTR wzPath,
91 __out_z LPWSTR *psczDirectory
92 )
93 {
94 HRESULT hr = S_OK;
95 LPCWSTR wzRemaining = NULL;
96 SIZE_T cchDirectory = 0;
97
98 for (LPCWSTR wz = wzPath; *wz; ++wz)
99 {
100 // valid delineators:
101 // \ => Windows path
102 // / => unix and URL path
103 // : => relative path from mapped root
104 if (IsPathSeparatorChar(*wz) || (L':' == *wz && wz == wzPath + 1))
105 {
106 wzRemaining = wz;
107 }
108 }
109
110 if (!wzRemaining)
111 {
112 // we were given just a file name, so there's no directory available
113 ExitFunction1(hr = S_FALSE);
114 }
115
116 cchDirectory = static_cast<SIZE_T>(wzRemaining - wzPath) + 1;
117
118 hr = StrAllocString(psczDirectory, wzPath, cchDirectory);
119 PathExitOnFailure(hr, "Failed to copy directory.");
120
121 LExit:
122 return hr;
123 }
124
125
126 DAPI_(HRESULT) PathGetParentPath(
127 __in_z LPCWSTR wzPath,
128 __out_z LPWSTR* psczParent,
129 __out_opt SIZE_T* pcchRoot
130 )
131 {
132 HRESULT hr = S_OK;
133 LPCWSTR wzPastRoot = NULL;
134 LPCWSTR wzParent = NULL;
135 LPCWSTR wz = NULL;
136
137 wzPastRoot = PathSkipPastRoot(wzPath, NULL, NULL, NULL);
138
139 if (pcchRoot)
140 {
141 *pcchRoot = !wzPastRoot ? 0 : wzPastRoot - wzPath;
142 }
143
144 if (wzPastRoot && *wzPastRoot)
145 {
146 Assert(wzPastRoot > wzPath);
147 wz = wzPastRoot;
148 wzParent = wzPastRoot - 1;
149 }
150 else
151 {
152 wz = wzPath;
153 }
154
155 for (; *wz; ++wz)
156 {
157 if (IsPathSeparatorChar(*wz) && wz[1])
158 {
159 wzParent = wz;
160 }
161 }
162
163 if (wzParent)
164 {
165 size_t cchPath = static_cast<size_t>(wzParent - wzPath) + 1;
166
167 hr = StrAllocString(psczParent, wzPath, cchPath);
168 PathExitOnFailure(hr, "Failed to copy directory.");
169 }
170 else
171 {
172 ReleaseNullStr(*psczParent);
173 }
174
175 LExit:
176 return hr;
177 }
178
179
180 DAPI_(HRESULT) PathExpand(
181 __out LPWSTR *psczFullPath,
182 __in_z LPCWSTR wzRelativePath,
183 __in DWORD dwResolveFlags
184 )
185 {
186 Assert(wzRelativePath);
187
188 HRESULT hr = S_OK;
189 LPWSTR sczExpandedPath = NULL;
190 SIZE_T cchWritten = 0;
191 LPWSTR sczFullPath = NULL;
192 DWORD dwPrefixFlags = 0;
193
194 //
195 // First, expand any environment variables.
196 //
197 if (dwResolveFlags & PATH_EXPAND_ENVIRONMENT)
198 {
199 hr = EnvExpandEnvironmentStrings(wzRelativePath, &sczExpandedPath, &cchWritten);
200 PathExitOnFailure(hr, "Failed to expand environment variables in string: %ls", wzRelativePath);
201 }
202
203 //
204 // Second, get the full path.
205 //
206 if (dwResolveFlags & PATH_EXPAND_FULLPATH)
207 {
208 LPCWSTR wzPath = sczExpandedPath ? sczExpandedPath : wzRelativePath;
209
210 hr = PathGetFullPathName(wzPath, &sczFullPath, NULL, &cchWritten);
211 PathExitOnFailure(hr, "Failed to get full path for string: %ls", wzPath);
212
213 dwPrefixFlags |= PATH_PREFIX_EXPECT_FULLY_QUALIFIED;
214 }
215 else
216 {
217 sczFullPath = sczExpandedPath;
218 sczExpandedPath = NULL;
219 }
220
221 if (dwResolveFlags)
222 {
223 hr = PathPrefix(&sczFullPath, cchWritten, dwPrefixFlags);
224 PathExitOnFailure(hr, "Failed to prefix path after expanding.");
225 }
226
227 hr = StrAllocString(psczFullPath, sczFullPath ? sczFullPath : wzRelativePath, 0);
228 PathExitOnFailure(hr, "Failed to copy relative path into full path.");
229
230 LExit:
231 ReleaseStr(sczFullPath);
232 ReleaseStr(sczExpandedPath);
233
234 return hr;
235 }
236
237
238 DAPI_(HRESULT) PathGetFullPathName(
239 __in_z LPCWSTR wzPath,
240 __deref_out_z LPWSTR* psczFullPath,
241 __inout_z_opt LPCWSTR* pwzFileName,
242 __out_opt SIZE_T* pcch
243 )
244 {
245 HRESULT hr = S_OK;
246 SIZE_T cchMax = 0;
247 DWORD cchBuffer = 0;
248 DWORD cch = 0;
249 DWORD dwAttempts = 0;
250 const DWORD dwMaxAttempts = 10;
251
252 if (!wzPath || !*wzPath)
253 {
254 hr = DirGetCurrent(psczFullPath, pcch);
255 PathExitOnFailure(hr, "Failed to get current directory.");
256
257 ExitFunction();
258 }
259
260 if (*psczFullPath)
261 {
262 hr = StrMaxLength(*psczFullPath, &cchMax);
263 PathExitOnFailure(hr, "Failed to get max length of input buffer.");
264
265 cchBuffer = (DWORD)min(DWORD_MAX, cchMax);
266 }
267 else
268 {
269 cchBuffer = MAX_PATH + 1;
270
271 hr = StrAlloc(psczFullPath, cchBuffer);
272 PathExitOnFailure(hr, "Failed to allocate space for full path.");
273 }
274
275 for (; dwAttempts < dwMaxAttempts; ++dwAttempts)
276 {
277 cch = ::GetFullPathNameW(wzPath, cchBuffer, *psczFullPath, const_cast<LPWSTR*>(pwzFileName));
278 PathExitOnNullWithLastError(cch, hr, "Failed to get full path for string: %ls", wzPath);
279
280 if (cch < cchBuffer)
281 {
282 break;
283 }
284
285 hr = StrAlloc(psczFullPath, cch);
286 PathExitOnFailure(hr, "Failed to reallocate space for full path.");
287
288 cchBuffer = cch;
289 }
290
291 if (dwMaxAttempts == dwAttempts)
292 {
293 PathExitWithRootFailure(hr, E_INSUFFICIENT_BUFFER, "GetFullPathNameW results never converged.");
294 }
295
296 if (pcch)
297 {
298 *pcch = cch;
299 }
300
301 LExit:
302 return hr;
303 }
304
305
306 DAPI_(HRESULT) PathPrefix(
307 __inout_z LPWSTR* psczFullPath,
308 __in SIZE_T cchFullPath,
309 __in DWORD dwPrefixFlags
310 )
311 {
312 Assert(psczFullPath);
313
314 HRESULT hr = S_OK;
315 LPWSTR wzFullPath = *psczFullPath;
316 BOOL fFullyQualified = FALSE;
317 BOOL fHasPrefix = FALSE;
318 BOOL fUNC = FALSE;
319 SIZE_T cbFullPath = 0;
320
321 PathSkipPastRoot(wzFullPath, &fHasPrefix, &fFullyQualified, &fUNC);
322
323 if (fHasPrefix)
324 {
325 ExitFunction();
326 }
327
328 // The prefix is only allowed on fully qualified paths.
329 if (!fFullyQualified)
330 {
331 if (dwPrefixFlags & PATH_PREFIX_EXPECT_FULLY_QUALIFIED)
332 {
333 PathExitWithRootFailure(hr, E_INVALIDARG, "Expected fully qualified path provided to prefix: %ls.", wzFullPath);
334 }
335
336 ExitFunction();
337 }
338
339 if (!(dwPrefixFlags & PATH_PREFIX_SHORT_PATHS))
340 {
341 // The prefix is not necessary unless the path is longer than MAX_PATH.
342 if (!cchFullPath)
343 {
344 hr = ::StringCchLengthW(wzFullPath, STRSAFE_MAX_CCH, reinterpret_cast<size_t*>(&cchFullPath));
345 PathExitOnFailure(hr, "Failed to get length of path to prefix.");
346 }
347
348 if (MAX_PATH >= cchFullPath)
349 {
350 ExitFunction();
351 }
352 }
353
354 if (fUNC)
355 {
356 hr = StrSize(*psczFullPath, &cbFullPath);
357 PathExitOnFailure(hr, "Failed to get size of full path.");
358
359 memmove_s(wzFullPath, cbFullPath, wzFullPath + 1, cbFullPath - sizeof(WCHAR));
360 wzFullPath[0] = L'\\';
361
362 hr = StrAllocPrefix(psczFullPath, L"\\\\?\\UNC", 7);
363 PathExitOnFailure(hr, "Failed to add prefix to UNC path.");
364 }
365 else // must be a normal path
366 {
367 hr = StrAllocPrefix(psczFullPath, L"\\\\?\\", 4);
368 PathExitOnFailure(hr, "Failed to add prefix to file path.");
369 }
370
371 LExit:
372 return hr;
373 }
374
375
376 DAPI_(HRESULT) PathFixedNormalizeSlashes(
377 __inout_z LPWSTR wzPath
378 )
379 {
380 HRESULT hr = S_OK;
381 size_t cchLength = 0;
382 BOOL fAllowDoubleSlash = FALSE;
383 SIZE_T iSource = 0;
384 SIZE_T jDestination = 0;
385
386 hr = ::StringCchLengthW(wzPath, STRSAFE_MAX_CCH, &cchLength);
387 PathExitOnFailure(hr, "Failed to get length of path.");
388
389 if (1 < cchLength && IsPathSeparatorChar(wzPath[0]))
390 {
391 if (IsPathSeparatorChar(wzPath[1]))
392 {
393 // \\?\\a\ is not equivalent to \\?\a\ and \\server\\a\ is not equivalent to \\server\a\.
394 fAllowDoubleSlash = TRUE;
395 wzPath[0] = '\\';
396 wzPath[1] = '\\';
397 iSource = 2;
398 jDestination = 2;
399 }
400 else if (2 < cchLength && L'?' == wzPath[1] && L'?' == wzPath[2])
401 {
402 // \??\\a\ is not equivalent to \??\a\.
403 fAllowDoubleSlash = TRUE;
404 wzPath[0] = '\\';
405 wzPath[1] = '?';
406 wzPath[2] = '?';
407 iSource = 3;
408 jDestination = 3;
409 }
410 }
411
412 for (; iSource < cchLength; ++iSource)
413 {
414 if (IsPathSeparatorChar(wzPath[iSource]))
415 {
416 if (fAllowDoubleSlash)
417 {
418 fAllowDoubleSlash = FALSE;
419 }
420 else if (IsPathSeparatorChar(wzPath[iSource + 1]))
421 {
422 // Skip consecutive slashes.
423 continue;
424 }
425
426 wzPath[jDestination] = '\\';
427 }
428 else
429 {
430 wzPath[jDestination] = wzPath[iSource];
431 }
432
433 ++jDestination;
434 }
435
436 for (; jDestination < cchLength; ++jDestination)
437 {
438 wzPath[jDestination] = '\0';
439 }
440
441 LExit:
442 return hr;
443 }
444
445
446 DAPI_(void) PathFixedReplaceForwardSlashes(
447 __inout_z LPWSTR wzPath
448 )
449 {
450 for (LPWSTR wz = wzPath; *wz; ++wz)
451 {
452 if (L'/' == *wz)
453 {
454 *wz = L'\\';
455 }
456 }
457 }
458
459
460 DAPI_(HRESULT) PathFixedBackslashTerminate(
461 __inout_ecount_z(cchPath) LPWSTR wzPath,
462 __in SIZE_T cchPath
463 )
464 {
465 HRESULT hr = S_OK;
466 size_t cchLength = 0;
467
468 if (!cchPath)
469 {
470 ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER));
471 }
472
473 hr = ::StringCchLengthW(wzPath, cchPath, &cchLength);
474 PathExitOnFailure(hr, "Failed to get length of path.");
475
476 LPWSTR wzLast = wzPath + (cchLength - 1);
477 if (cchLength && L'/' == wzLast[0])
478 {
479 wzLast[0] = L'\\';
480 }
481 else if (!cchLength || L'\\' != wzLast[0])
482 {
483 if (cchLength + 2 > cchPath)
484 {
485 ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER));
486 }
487
488 wzLast[1] = L'\\';
489 wzLast[2] = L'\0';
490 }
491
492 LExit:
493 return hr;
494 }
495
496
497 DAPI_(HRESULT) PathBackslashTerminate(
498 __inout_z LPWSTR* psczPath
499 )
500 {
501 Assert(psczPath);
502
503 HRESULT hr = S_OK;
504 SIZE_T cchPath = 0;
505 size_t cchLength = 0;
506
507 hr = StrMaxLength(*psczPath, &cchPath);
508 PathExitOnFailure(hr, "Failed to get size of path string.");
509
510 hr = ::StringCchLengthW(*psczPath, cchPath, &cchLength);
511 PathExitOnFailure(hr, "Failed to get length of path.");
512
513 LPWSTR wzLast = *psczPath + (cchLength - 1);
514 if (cchLength && L'/' == wzLast[0])
515 {
516 wzLast[0] = L'\\';
517 }
518 else if (!cchLength || L'\\' != wzLast[0])
519 {
520 hr = StrAllocConcat(psczPath, L"\\", 1);
521 PathExitOnFailure(hr, "Failed to concat backslash onto string.");
522 }
523
524 LExit:
525 return hr;
526 }
527
528
529 DAPI_(HRESULT) PathForCurrentProcess(
530 __inout LPWSTR *psczFullPath,
531 __in_opt HMODULE hModule
532 )
533 {
534 HRESULT hr = S_OK;
535 WCHAR smallBuffer[1] = { };
536 SIZE_T cchMax = 0;
537 DWORD cchBuffer = 0;
538 DWORD cch = 0;
539 DWORD dwMaxAttempts = 20;
540
541 // GetModuleFileNameW didn't originally set the last error when the buffer was too small.
542 ::SetLastError(ERROR_SUCCESS);
543
544 cch = ::GetModuleFileNameW(hModule, smallBuffer, countof(smallBuffer));
545 PathExitOnNullWithLastError(cch, hr, "Failed to get size of path for executing process.");
546
547 if (*psczFullPath && ERROR_INSUFFICIENT_BUFFER == ::GetLastError())
548 {
549 hr = StrMaxLength(*psczFullPath, &cchMax);
550 PathExitOnFailure(hr, "Failed to get max length of input buffer.");
551
552 cchBuffer = (DWORD)min(DWORD_MAX, cchMax);
553 }
554 else
555 {
556 cchBuffer = MAX_PATH + 1;
557
558 hr = StrAlloc(psczFullPath, cchBuffer);
559 PathExitOnFailure(hr, "Failed to allocate space for module path.");
560 }
561
562 ::SetLastError(ERROR_SUCCESS);
563
564 for (DWORD i = 0; i < dwMaxAttempts; ++i)
565 {
566 cch = ::GetModuleFileNameW(hModule, *psczFullPath, cchBuffer);
567 PathExitOnNullWithLastError(cch, hr, "Failed to get path for executing process.");
568
569 if (ERROR_INSUFFICIENT_BUFFER != ::GetLastError())
570 {
571 break;
572 }
573
574 if ((dwMaxAttempts - 1) == i)
575 {
576 PathExitWithRootFailure(hr, E_FAIL, "Unexpected failure getting path for executing process.");
577 }
578
579 cchBuffer *= 2;
580
581 hr = StrAlloc(psczFullPath, cchBuffer);
582 PathExitOnFailure(hr, "Failed to re-allocate more space for module path.");
583 }
584
585 LExit:
586 return hr;
587 }
588
589
590 DAPI_(HRESULT) PathRelativeToModule(
591 __inout LPWSTR *psczFullPath,
592 __in_opt LPCWSTR wzFileName,
593 __in_opt HMODULE hModule
594 )
595 {
596 HRESULT hr = PathForCurrentProcess(psczFullPath, hModule);
597 PathExitOnFailure(hr, "Failed to get current module path.");
598
599 hr = PathGetDirectory(*psczFullPath, psczFullPath);
600 PathExitOnFailure(hr, "Failed to get current module directory.");
601
602 if (wzFileName)
603 {
604 hr = PathConcat(*psczFullPath, wzFileName, psczFullPath);
605 PathExitOnFailure(hr, "Failed to append filename.");
606 }
607
608 LExit:
609 return hr;
610 }
611
612
613 DAPI_(HRESULT) PathCreateTempFile(
614 __in_opt LPCWSTR wzDirectory,
615 __in_opt __format_string LPCWSTR wzFileNameTemplate,
616 __in DWORD dwUniqueCount,
617 __in_z LPCWSTR wzPrefix,
618 __in DWORD dwFileAttributes,
619 __out_opt LPWSTR* psczTempFile,
620 __out_opt HANDLE* phTempFile
621 )
622 {
623 Assert(wzPrefix);
624 AssertSz(!wzFileNameTemplate || !*wzFileNameTemplate || 0 < dwUniqueCount, "Must specify a non-zero unique count.");
625
626 HRESULT hr = S_OK;
627
628 LPWSTR sczTempPath = NULL;
629
630 HANDLE hTempFile = INVALID_HANDLE_VALUE;
631 LPWSTR scz = NULL;
632 LPWSTR sczTempFile = NULL;
633
634 if (wzDirectory && *wzDirectory)
635 {
636 hr = StrAllocString(&sczTempPath, wzDirectory, 0);
637 PathExitOnFailure(hr, "Failed to copy temp path.");
638 }
639 else
640 {
641 hr = PathGetTempPath(&sczTempPath, NULL);
642 PathExitOnFailure(hr, "Failed to get temp path.");
643 }
644
645 if (wzFileNameTemplate && *wzFileNameTemplate)
646 {
647 for (DWORD i = 1; i <= dwUniqueCount && INVALID_HANDLE_VALUE == hTempFile; ++i)
648 {
649 hr = StrAllocFormatted(&scz, wzFileNameTemplate, i);
650 PathExitOnFailure(hr, "Failed to allocate memory for file template.");
651
652 hr = StrAllocFormatted(&sczTempFile, L"%ls%ls", sczTempPath, scz);
653 PathExitOnFailure(hr, "Failed to allocate temp file name.");
654
655 hTempFile = ::CreateFileW(sczTempFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, CREATE_NEW, dwFileAttributes, NULL);
656 if (INVALID_HANDLE_VALUE == hTempFile)
657 {
658 // if the file already exists, just try again
659 hr = HRESULT_FROM_WIN32(::GetLastError());
660 if (HRESULT_FROM_WIN32(ERROR_FILE_EXISTS) == hr)
661 {
662 hr = S_OK;
663 }
664 PathExitOnFailure(hr, "Failed to create file: %ls", sczTempFile);
665 }
666 }
667 }
668
669 // If we were not able to or we did not try to create a temp file, ask
670 // the system to provide us a temp file using its built-in mechanism.
671 if (INVALID_HANDLE_VALUE == hTempFile)
672 {
673 hr = PathGetTempFileName(sczTempPath, wzPrefix, 0, &sczTempFile);
674 PathExitOnFailure(hr, "Failed to create new temp file name.");
675
676 hTempFile = ::CreateFileW(sczTempFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, dwFileAttributes, NULL);
677 if (INVALID_HANDLE_VALUE == hTempFile)
678 {
679 PathExitWithLastError(hr, "Failed to open new temp file: %ls", sczTempFile);
680 }
681 }
682
683 // If the caller wanted the temp file name or handle, return them here.
684 if (psczTempFile)
685 {
686 hr = StrAllocString(psczTempFile, sczTempFile, 0);
687 PathExitOnFailure(hr, "Failed to copy temp file string.");
688 }
689
690 if (phTempFile)
691 {
692 *phTempFile = hTempFile;
693 hTempFile = INVALID_HANDLE_VALUE;
694 }
695
696 LExit:
697 if (INVALID_HANDLE_VALUE != hTempFile)
698 {
699 ::CloseHandle(hTempFile);
700 }
701
702 ReleaseStr(scz);
703 ReleaseStr(sczTempFile);
704 ReleaseStr(sczTempPath);
705
706 return hr;
707 }
708
709
710 DAPI_(HRESULT) PathGetTempFileName(
711 __in LPCWSTR wzPathName,
712 __in LPCWSTR wzPrefixString,
713 __in UINT uUnique,
714 __out LPWSTR* psczTempFileName
715 )
716 {
717 HRESULT hr = S_OK;
718 size_t cchFullPath = 0;
719 WORD wValue = (WORD)(0xffff & uUnique);
720 LPWSTR scz = NULL;
721 LPWSTR sczTempFile = NULL;
722 HANDLE hTempFile = INVALID_HANDLE_VALUE;
723
724 hr = ::StringCchLengthW(wzPathName, STRSAFE_MAX_CCH, &cchFullPath);
725 PathExitOnFailure(hr, "Failed to get length of path to prefix.");
726
727 if (MAX_PATH - 14 >= cchFullPath)
728 {
729 hr = StrAlloc(psczTempFileName, MAX_PATH);
730 PathExitOnFailure(hr, "Failed to allocate buffer for GetTempFileNameW.");
731
732 if (!::GetTempFileNameW(wzPathName, wzPrefixString, uUnique, *psczTempFileName))
733 {
734 PathExitWithLastError(hr, "Failed to create new temp file name.");
735 }
736
737 ExitFunction();
738 }
739
740 // TODO: when uUnique is 0, consider not always starting at 0 to avoid collisions if this is called repeatedly.
741 // Purposely let it wrap around.
742 for (WORD w = 0; w < WORD_MAX && INVALID_HANDLE_VALUE == hTempFile; ++wValue)
743 {
744 hr = StrAllocFormatted(&scz, L"%ls%x.TMP", wzPrefixString, w);
745 PathExitOnFailure(hr, "Failed to allocate memory for file template.");
746
747 hr = PathConcat(wzPathName, scz, &sczTempFile);
748 PathExitOnFailure(hr, "Failed to allocate temp file name.");
749
750 hTempFile = ::CreateFileW(sczTempFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, CREATE_NEW, 0, NULL);
751 if (INVALID_HANDLE_VALUE == hTempFile)
752 {
753 // if the file already exists, try next one.
754 hr = HRESULT_FROM_WIN32(::GetLastError());
755 if (HRESULT_FROM_WIN32(ERROR_FILE_EXISTS) == hr)
756 {
757 hr = S_OK;
758 }
759 PathExitOnFailure(hr, "Failed to create file: %ls", sczTempFile);
760 }
761
762 ++w;
763 }
764
765 if (INVALID_HANDLE_VALUE == hTempFile)
766 {
767 PathExitWithRootFailure(hr, HRESULT_FROM_WIN32(ERROR_FILE_EXISTS), "Failed to create temp file.");
768 }
769
770 hr = StrAllocString(psczTempFileName, sczTempFile, 0);
771 PathExitOnFailure(hr, "Failed to copy temp file string.");
772
773 LExit:
774 if (INVALID_HANDLE_VALUE != hTempFile)
775 {
776 ::CloseHandle(hTempFile);
777 }
778
779 ReleaseStr(scz);
780 ReleaseStr(sczTempFile);
781
782 return hr;
783 }
784
785
786 DAPI_(HRESULT) PathCreateTimeBasedTempFile(
787 __in_z_opt LPCWSTR wzDirectory,
788 __in_z LPCWSTR wzPrefix,
789 __in_z_opt LPCWSTR wzPostfix,
790 __in_z LPCWSTR wzExtension,
791 __deref_opt_out_z LPWSTR* psczTempFile,
792 __out_opt HANDLE* phTempFile
793 )
794 {
795 HRESULT hr = S_OK;
796 BOOL fRetry = FALSE;
797 LPWSTR sczTempParentPath = NULL;
798 LPWSTR sczPrefix = NULL;
799 LPWSTR sczPrefixFolder = NULL;
800 SYSTEMTIME time = { };
801
802 LPWSTR sczTempPath = NULL;
803 HANDLE hTempFile = INVALID_HANDLE_VALUE;
804 DWORD dwAttempts = 0;
805
806 if (wzDirectory && *wzDirectory)
807 {
808 hr = PathConcat(wzDirectory, wzPrefix, &sczPrefix);
809 PathExitOnFailure(hr, "Failed to combine directory and log prefix.");
810 }
811 else
812 {
813 hr = PathGetTempPath(&sczTempParentPath, NULL);
814 PathExitOnFailure(hr, "Failed to get temp folder.");
815
816 hr = PathConcat(sczTempParentPath, wzPrefix, &sczPrefix);
817 PathExitOnFailure(hr, "Failed to concatenate the temp folder and log prefix.");
818 }
819
820 hr = PathGetDirectory(sczPrefix, &sczPrefixFolder);
821 if (S_OK == hr)
822 {
823 hr = DirEnsureExists(sczPrefixFolder, NULL);
824 PathExitOnFailure(hr, "Failed to ensure temp file path exists: %ls", sczPrefixFolder);
825 }
826
827 if (!wzPostfix)
828 {
829 wzPostfix = L"";
830 }
831
832 do
833 {
834 fRetry = FALSE;
835 ++dwAttempts;
836
837 ::GetLocalTime(&time);
838
839 // Log format: pre YYYY MM dd hh mm ss post ext
840 hr = StrAllocFormatted(&sczTempPath, L"%ls_%04u%02u%02u%02u%02u%02u%ls%ls%ls", sczPrefix, time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond, wzPostfix, L'.' == *wzExtension ? L"" : L".", wzExtension);
841 PathExitOnFailure(hr, "failed to allocate memory for the temp path");
842
843 hTempFile = ::CreateFileW(sczTempPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
844 if (INVALID_HANDLE_VALUE == hTempFile)
845 {
846 // If the file already exists, just try again.
847 DWORD er = ::GetLastError();
848 if (ERROR_FILE_EXISTS == er || ERROR_ACCESS_DENIED == er)
849 {
850 ::Sleep(100);
851
852 if (10 > dwAttempts)
853 {
854 er = ERROR_SUCCESS;
855 fRetry = TRUE;
856 }
857 }
858
859 hr = HRESULT_FROM_WIN32(er);
860 PathExitOnFailureDebugTrace(hr, "Failed to create temp file: %ls", sczTempPath);
861 }
862 } while (fRetry);
863
864 if (psczTempFile)
865 {
866 hr = StrAllocString(psczTempFile, sczTempPath, 0);
867 PathExitOnFailure(hr, "Failed to copy temp path to return.");
868 }
869
870 if (phTempFile)
871 {
872 *phTempFile = hTempFile;
873 hTempFile = INVALID_HANDLE_VALUE;
874 }
875
876 LExit:
877 ReleaseFile(hTempFile);
878 ReleaseStr(sczTempParentPath);
879 ReleaseStr(sczTempPath);
880 ReleaseStr(sczPrefixFolder);
881 ReleaseStr(sczPrefix);
882
883 return hr;
884 }
885
886
887 DAPI_(HRESULT) PathCreateTempDirectory(
888 __in_opt LPCWSTR wzDirectory,
889 __in __format_string LPCWSTR wzDirectoryNameTemplate,
890 __in DWORD dwUniqueCount,
891 __out LPWSTR* psczTempDirectory
892 )
893 {
894 AssertSz(wzDirectoryNameTemplate && *wzDirectoryNameTemplate, "DirectoryNameTemplate must be specified.");
895 AssertSz(0 < dwUniqueCount, "Must specify a non-zero unique count.");
896
897 HRESULT hr = S_OK;
898
899 LPWSTR sczTempPath = NULL;
900
901 LPWSTR scz = NULL;
902
903 if (wzDirectory && *wzDirectory)
904 {
905 hr = StrAllocString(&sczTempPath, wzDirectory, 0);
906 PathExitOnFailure(hr, "Failed to copy temp path.");
907
908 hr = PathBackslashTerminate(&sczTempPath);
909 PathExitOnFailure(hr, "Failed to ensure path ends in backslash: %ls", wzDirectory);
910 }
911 else
912 {
913 hr = PathGetTempPath(&sczTempPath, NULL);
914 PathExitOnFailure(hr, "Failed to get temp path.");
915 }
916
917 for (DWORD i = 1; i <= dwUniqueCount; ++i)
918 {
919 hr = StrAllocFormatted(&scz, wzDirectoryNameTemplate, i);
920 PathExitOnFailure(hr, "Failed to allocate memory for directory name template.");
921
922 hr = StrAllocFormatted(psczTempDirectory, L"%s%s", sczTempPath, scz);
923 PathExitOnFailure(hr, "Failed to allocate temp directory name.");
924
925 if (!::CreateDirectoryW(*psczTempDirectory, NULL))
926 {
927 DWORD er = ::GetLastError();
928 if (ERROR_ALREADY_EXISTS == er)
929 {
930 hr = HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS);
931 continue;
932 }
933 else if (ERROR_PATH_NOT_FOUND == er)
934 {
935 hr = DirEnsureExists(*psczTempDirectory, NULL);
936 break;
937 }
938 else
939 {
940 hr = HRESULT_FROM_WIN32(er);
941 break;
942 }
943 }
944 else
945 {
946 hr = S_OK;
947 break;
948 }
949 }
950 PathExitOnFailure(hr, "Failed to create temp directory.");
951
952 hr = PathBackslashTerminate(psczTempDirectory);
953 PathExitOnFailure(hr, "Failed to ensure temp directory is backslash terminated.");
954
955 LExit:
956 ReleaseStr(scz);
957 ReleaseStr(sczTempPath);
958
959 return hr;
960 }
961
962
963 DAPI_(HRESULT) PathGetTempPath(
964 __out_z LPWSTR* psczTempPath,
965 __out_opt SIZE_T* pcch
966 )
967 {
968
969 HRESULT hr = S_OK;
970 SIZE_T cchMax = 0;
971 DWORD cchBuffer = 0;
972 DWORD cch = 0;
973 DWORD dwAttempts = 0;
974 HMODULE hModule = NULL;
975 PFN_GETTEMPPATH2W pfnGetTempPath = NULL;
976 const DWORD dwMaxAttempts = 10;
977
978 if (*psczTempPath)
979 {
980 hr = StrMaxLength(*psczTempPath, &cchMax);
981 PathExitOnFailure(hr, "Failed to get max length of input buffer.");
982
983 cchBuffer = (DWORD)min(DWORD_MAX, cchMax);
984 }
985 else
986 {
987 cchBuffer = MAX_PATH + 1;
988
989 hr = StrAlloc(psczTempPath, cchBuffer);
990 PathExitOnFailure(hr, "Failed to allocate space for temp path.");
991 }
992
993 hr = LoadSystemLibrary(L"kernel32.dll", &hModule);
994 PathExitOnFailure(hr, "Failed to load kernel32.dll");
995
996 pfnGetTempPath = reinterpret_cast<PFN_GETTEMPPATH2W>(::GetProcAddress(hModule, "GetTempPath2W"));
997 if (!pfnGetTempPath)
998 {
999 pfnGetTempPath = ::GetTempPathW;
1000 }
1001
1002 for (; dwAttempts < dwMaxAttempts; ++dwAttempts)
1003 {
1004 cch = pfnGetTempPath(cchBuffer, *psczTempPath);
1005 PathExitOnNullWithLastError(cch, hr, "Failed to get temp path.");
1006
1007 cch += 1; // add one for null terminator.
1008
1009 if (cch <= cchBuffer)
1010 {
1011 break;
1012 }
1013
1014 hr = StrAlloc(psczTempPath, cch);
1015 PathExitOnFailure(hr, "Failed to reallocate space for temp path.");
1016
1017 cchBuffer = cch;
1018 }
1019
1020 if (dwMaxAttempts == dwAttempts)
1021 {
1022 PathExitWithRootFailure(hr, E_INSUFFICIENT_BUFFER, "GetTempPathW results never converged.");
1023 }
1024
1025 if (pcch)
1026 {
1027 *pcch = cch - 1; // remove one for null terminator.
1028 }
1029
1030 LExit:
1031 return hr;
1032 }
1033
1034
1035 DAPI_(HRESULT) PathGetSystemDirectory(
1036 __out_z LPWSTR* psczSystemPath
1037 )
1038 {
1039 HRESULT hr = S_OK;
1040 SIZE_T cchMax = 0;
1041 DWORD cchBuffer = 0;
1042 DWORD cch = 0;
1043
1044 if (*psczSystemPath)
1045 {
1046 hr = StrMaxLength(*psczSystemPath, &cchMax);
1047 PathExitOnFailure(hr, "Failed to get max length of input buffer.");
1048
1049 cchBuffer = (DWORD)min(DWORD_MAX, cchMax);
1050 }
1051 else
1052 {
1053 cchBuffer = MAX_PATH + 1;
1054
1055 hr = StrAlloc(psczSystemPath, cchBuffer);
1056 PathExitOnFailure(hr, "Failed to allocate space for system directory.");
1057 }
1058
1059 cch = ::GetSystemDirectoryW(*psczSystemPath, cchBuffer);
1060 PathExitOnNullWithLastError(cch, hr, "Failed to get system directory path with default size.");
1061
1062 if (cch > cchBuffer)
1063 {
1064 hr = StrAlloc(psczSystemPath, cch);
1065 PathExitOnFailure(hr, "Failed to realloc system directory path.");
1066
1067 cchBuffer = cch;
1068
1069 cch = ::GetSystemDirectoryW(*psczSystemPath, cchBuffer);
1070 PathExitOnNullWithLastError(cch, hr, "Failed to get system directory path with returned size.");
1071
1072 if (cch > cchBuffer)
1073 {
1074 PathExitWithRootFailure(hr, E_INSUFFICIENT_BUFFER, "Failed to get system directory path with returned size.");
1075 }
1076 }
1077
1078 hr = PathBackslashTerminate(psczSystemPath);
1079 PathExitOnFailure(hr, "Failed to terminate system directory path with backslash.");
1080
1081 LExit:
1082 return hr;
1083 }
1084
1085
1086 DAPI_(HRESULT) PathGetSystemWow64Directory(
1087 __out_z LPWSTR* psczSystemPath
1088 )
1089 {
1090 HRESULT hr = S_OK;
1091 SIZE_T cchMax = 0;
1092 DWORD cchBuffer = 0;
1093 DWORD cch = 0;
1094
1095 if (*psczSystemPath)
1096 {
1097 hr = StrMaxLength(*psczSystemPath, &cchMax);
1098 PathExitOnFailure(hr, "Failed to get max length of input buffer.");
1099
1100 cchBuffer = (DWORD)min(DWORD_MAX, cchMax);
1101 }
1102 else
1103 {
1104 cchBuffer = MAX_PATH + 1;
1105
1106 hr = StrAlloc(psczSystemPath, cchBuffer);
1107 PathExitOnFailure(hr, "Failed to allocate space for system wow64 directory.");
1108 }
1109
1110 cch = ::GetSystemWow64DirectoryW(*psczSystemPath, cchBuffer);
1111 PathExitOnNullWithLastError(cch, hr, "Failed to get system wow64 directory path with default size.");
1112
1113 cch += 1; // add one for the null terminator.
1114
1115 if (cch > cchBuffer)
1116 {
1117 hr = StrAlloc(psczSystemPath, cch);
1118 PathExitOnFailure(hr, "Failed to realloc system wow64 directory path.");
1119
1120 cchBuffer = cch;
1121
1122 cch = ::GetSystemWow64DirectoryW(*psczSystemPath, cchBuffer);
1123 PathExitOnNullWithLastError(cch, hr, "Failed to get system wow64 directory path with returned size.");
1124
1125 cch += 1; // add one for the null terminator.
1126
1127 if (cch > cchBuffer)
1128 {
1129 PathExitWithRootFailure(hr, E_INSUFFICIENT_BUFFER, "Failed to get system wow64 directory path with returned size.");
1130 }
1131 }
1132
1133 hr = PathBackslashTerminate(psczSystemPath);
1134 PathExitOnFailure(hr, "Failed to terminate system wow64 directory path with backslash.");
1135
1136 LExit:
1137 return hr;
1138 }
1139
1140
1141 DAPI_(HRESULT) PathGetVolumePathName(
1142 __in_z LPCWSTR wzFileName,
1143 __out_z LPWSTR* psczVolumePathName
1144 )
1145 {
1146 HRESULT hr = S_OK;
1147 DWORD cchBuffer = 0;
1148 SIZE_T cchMax = 0;
1149 const DWORD dwMaxAttempts = 20;
1150
1151 if (*psczVolumePathName)
1152 {
1153 hr = StrMaxLength(*psczVolumePathName, &cchMax);
1154 PathExitOnFailure(hr, "Failed to get max length of input buffer.");
1155
1156 cchBuffer = (DWORD)min(DWORD_MAX, cchMax);
1157 }
1158 else
1159 {
1160 cchBuffer = MAX_PATH + 1;
1161
1162 hr = StrAlloc(psczVolumePathName, cchBuffer);
1163 PathExitOnFailure(hr, "Failed to allocate space for volume path name.");
1164 }
1165
1166 for (DWORD i = 0; i < dwMaxAttempts; ++i)
1167 {
1168 if (::GetVolumePathNameW(wzFileName, *psczVolumePathName, cchBuffer))
1169 {
1170 break;
1171 }
1172
1173 hr = HRESULT_FROM_WIN32(::GetLastError());
1174 if (HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) != hr && E_INSUFFICIENT_BUFFER != hr ||
1175 (dwMaxAttempts - 1) == i)
1176 {
1177 PathExitWithRootFailure(hr, hr, "Failed to get volume path name of: %ls", wzFileName);
1178 }
1179
1180 cchBuffer *= 2;
1181
1182 hr = StrAlloc(psczVolumePathName, cchBuffer);
1183 PathExitOnFailure(hr, "Failed to re-allocate more space for volume path name.");
1184 }
1185
1186 hr = PathBackslashTerminate(psczVolumePathName);
1187 PathExitOnFailure(hr, "Failed to terminate volume path name with backslash.");
1188
1189 LExit:
1190 return hr;
1191 }
1192
1193
1194 DAPI_(LPCWSTR) PathSkipPastRoot(
1195 __in_z_opt LPCWSTR wzPath,
1196 __out_opt BOOL* pfHasExtendedPrefix,
1197 __out_opt BOOL* pfFullyQualified,
1198 __out_opt BOOL* pfUNC
1199 )
1200 {
1201 LPCWSTR wzPastRoot = NULL;
1202 BOOL fHasPrefix = FALSE;
1203 BOOL fFullyQualified = FALSE;
1204 BOOL fUNC = FALSE;
1205 DWORD dwRootMissingSlashes = 0;
1206
1207 if (!wzPath || !*wzPath)
1208 {
1209 ExitFunction();
1210 }
1211
1212 if (IsPathSeparatorChar(wzPath[0]))
1213 {
1214 if (IsPathSeparatorChar(wzPath[1]) && (L'?' == wzPath[2] || L'.' == wzPath[2]) && IsPathSeparatorChar(wzPath[3]) ||
1215 L'?' == wzPath[1] && L'?' == wzPath[2] && IsPathSeparatorChar(wzPath[3]))
1216 {
1217 fHasPrefix = TRUE;
1218
1219 if (L'U' == wzPath[4] && L'N' == wzPath[5] && L'C' == wzPath[6] && IsPathSeparatorChar(wzPath[7]))
1220 {
1221 fUNC = TRUE;
1222 wzPastRoot = wzPath + 8;
1223 dwRootMissingSlashes = 2;
1224 }
1225 else
1226 {
1227 wzPastRoot = wzPath + 4;
1228 dwRootMissingSlashes = 1;
1229 }
1230 }
1231 else if (IsPathSeparatorChar(wzPath[1]))
1232 {
1233 fUNC = TRUE;
1234 wzPastRoot = wzPath + 2;
1235 dwRootMissingSlashes = 2;
1236 }
1237 }
1238
1239 if (dwRootMissingSlashes)
1240 {
1241 Assert(wzPastRoot);
1242 fFullyQualified = TRUE;
1243
1244 for (; *wzPastRoot && dwRootMissingSlashes; ++wzPastRoot)
1245 {
1246 if (IsPathSeparatorChar(*wzPastRoot))
1247 {
1248 --dwRootMissingSlashes;
1249 }
1250 }
1251 }
1252 else
1253 {
1254 Assert(!wzPastRoot);
1255
1256 if (IsPathSeparatorChar(wzPath[0]))
1257 {
1258 wzPastRoot = wzPath + 1;
1259 }
1260 else if (IsValidDriveChar(wzPath[0]) && wzPath[1] == L':')
1261 {
1262 if (IsPathSeparatorChar(wzPath[2]))
1263 {
1264 fFullyQualified = TRUE;
1265 wzPastRoot = wzPath + 3;
1266 }
1267 else
1268 {
1269 wzPastRoot = wzPath + 2;
1270 }
1271 }
1272 }
1273
1274 LExit:
1275 if (pfHasExtendedPrefix)
1276 {
1277 *pfHasExtendedPrefix = fHasPrefix;
1278 }
1279
1280 if (pfFullyQualified)
1281 {
1282 *pfFullyQualified = fFullyQualified;
1283 }
1284
1285 if (pfUNC)
1286 {
1287 *pfUNC = fUNC;
1288 }
1289
1290 return wzPastRoot;
1291 }
1292
1293
1294 DAPI_(BOOL) PathIsFullyQualified(
1295 __in_z LPCWSTR wzPath
1296 )
1297 {
1298 BOOL fFullyQualified = FALSE;
1299
1300 PathSkipPastRoot(wzPath, NULL, &fFullyQualified, NULL);
1301
1302 return fFullyQualified;
1303 }
1304
1305
1306 DAPI_(BOOL) PathIsRooted(
1307 __in_z LPCWSTR wzPath
1308 )
1309 {
1310 return NULL != PathSkipPastRoot(wzPath, NULL, NULL, NULL);
1311 }
1312
1313
1314 DAPI_(HRESULT) PathConcat(
1315 __in_opt LPCWSTR wzPath1,
1316 __in_opt LPCWSTR wzPath2,
1317 __deref_out_z LPWSTR* psczCombined
1318 )
1319 {
1320 return PathConcatCch(wzPath1, 0, wzPath2, 0, psczCombined);
1321 }
1322
1323
1324 DAPI_(HRESULT) PathConcatCch(
1325 __in_opt LPCWSTR wzPath1,
1326 __in SIZE_T cchPath1,
1327 __in_opt LPCWSTR wzPath2,
1328 __in SIZE_T cchPath2,
1329 __deref_out_z LPWSTR* psczCombined
1330 )
1331 {
1332 HRESULT hr = S_OK;
1333
1334 if (!wzPath2 || !*wzPath2)
1335 {
1336 hr = StrAllocString(psczCombined, wzPath1, cchPath1);
1337 PathExitOnFailure(hr, "Failed to copy just path1 to output.");
1338 }
1339 else if (!wzPath1 || !*wzPath1 || PathIsRooted(wzPath2))
1340 {
1341 hr = StrAllocString(psczCombined, wzPath2, cchPath2);
1342 PathExitOnFailure(hr, "Failed to copy just path2 to output.");
1343 }
1344 else
1345 {
1346 hr = StrAllocString(psczCombined, wzPath1, cchPath1);
1347 PathExitOnFailure(hr, "Failed to copy path1 to output.");
1348
1349 hr = PathBackslashTerminate(psczCombined);
1350 PathExitOnFailure(hr, "Failed to backslashify.");
1351
1352 hr = StrAllocConcat(psczCombined, wzPath2, cchPath2);
1353 PathExitOnFailure(hr, "Failed to append path2 to output.");
1354 }
1355
1356 LExit:
1357 return hr;
1358 }
1359
1360
1361 DAPI_(HRESULT) PathCompress(
1362 __in_z LPCWSTR wzPath
1363 )
1364 {
1365 HRESULT hr = S_OK;
1366 HANDLE hPath = INVALID_HANDLE_VALUE;
1367
1368 hPath = ::CreateFileW(wzPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
1369 if (INVALID_HANDLE_VALUE == hPath)
1370 {
1371 PathExitWithLastError(hr, "Failed to open path %ls for compression.", wzPath);
1372 }
1373
1374 DWORD dwBytesReturned = 0;
1375 USHORT usCompressionFormat = COMPRESSION_FORMAT_DEFAULT;
1376 if (0 == ::DeviceIoControl(hPath, FSCTL_SET_COMPRESSION, &usCompressionFormat, sizeof(usCompressionFormat), NULL, 0, &dwBytesReturned, NULL))
1377 {
1378 // ignore compression attempts on file systems that don't support it
1379 DWORD er = ::GetLastError();
1380 if (ERROR_INVALID_FUNCTION != er)
1381 {
1382 PathExitOnWin32Error(er, hr, "Failed to set compression state for path %ls.", wzPath);
1383 }
1384 }
1385
1386 LExit:
1387 ReleaseFile(hPath);
1388
1389 return hr;
1390 }
1391
1392 DAPI_(HRESULT) PathGetHierarchyArray(
1393 __in_z LPCWSTR wzPath,
1394 __deref_inout_ecount_opt(*pcPathArray) LPWSTR **prgsczPathArray,
1395 __inout LPUINT pcPathArray
1396 )
1397 {
1398 HRESULT hr = S_OK;
1399 LPCWSTR wz = NULL;
1400 SIZE_T cch = 0;
1401 *pcPathArray = 0;
1402
1403 PathExitOnNull(wzPath, hr, E_INVALIDARG, "wzPath is required.");
1404
1405 wz = PathSkipPastRoot(wzPath, NULL, NULL, NULL);
1406 if (wz)
1407 {
1408 cch = wz - wzPath;
1409
1410 hr = MemEnsureArraySize(reinterpret_cast<void**>(prgsczPathArray), 1, sizeof(LPWSTR), 5);
1411 PathExitOnFailure(hr, "Failed to allocate array.");
1412
1413 hr = StrAllocString(*prgsczPathArray, wzPath, cch);
1414 PathExitOnFailure(hr, "Failed to copy root into array.");
1415
1416 *pcPathArray += 1;
1417 }
1418 else
1419 {
1420 wz = wzPath;
1421 }
1422
1423 for (; *wz; ++wz)
1424 {
1425 ++cch;
1426
1427 if (IsPathSeparatorChar(*wz) || !wz[1])
1428 {
1429 hr = MemEnsureArraySizeForNewItems(reinterpret_cast<void**>(prgsczPathArray), *pcPathArray, 1, sizeof(LPWSTR), 5);
1430 PathExitOnFailure(hr, "Failed to allocate array.");
1431
1432 hr = StrAllocString(*prgsczPathArray + *pcPathArray, wzPath, cch);
1433 PathExitOnFailure(hr, "Failed to copy path into array.");
1434
1435 *pcPathArray += 1;
1436 }
1437 }
1438
1439 LExit:
1440 return hr;
1441 }
1442
1443 static BOOL IsPathSeparatorChar(
1444 __in WCHAR wc
1445 )
1446 {
1447 return L'/' == wc || L'\\' == wc;
1448 }
1449
1450 static BOOL IsValidDriveChar(
1451 __in WCHAR wc
1452 )
1453 {
1454 return L'a' <= wc && L'z' >= wc ||
1455 L'A' <= wc && L'Z' >= wc;
1456 }