aboutsummaryrefslogtreecommitdiff
path: root/src/dutil/pathutil.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/dutil/pathutil.cpp')
-rw-r--r--src/dutil/pathutil.cpp1009
1 files changed, 1009 insertions, 0 deletions
diff --git a/src/dutil/pathutil.cpp b/src/dutil/pathutil.cpp
new file mode 100644
index 00000000..c508dd32
--- /dev/null
+++ b/src/dutil/pathutil.cpp
@@ -0,0 +1,1009 @@
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#define PATH_GOOD_ENOUGH 64
6
7
8DAPI_(HRESULT) PathCommandLineAppend(
9 __deref_out_z LPWSTR* psczCommandLine,
10 __in_z LPCWSTR wzArgument
11 )
12{
13 HRESULT hr = S_OK;
14 LPWSTR sczQuotedArg = NULL;
15 BOOL fRequiresQuoting = FALSE;
16 DWORD dwMaxEscapedSize = 0;
17
18 // Loop through the argument determining if it needs to be quoted and what the maximum
19 // size would be if there are escape characters required.
20 for (LPCWSTR pwz = wzArgument; *pwz; ++pwz)
21 {
22 // Arguments with whitespace need quoting.
23 if (L' ' == *pwz || L'\t' == *pwz || L'\n' == *pwz || L'\v' == *pwz)
24 {
25 fRequiresQuoting = TRUE;
26 }
27 else if (L'"' == *pwz) // quotes need quoting and sometimes escaping.
28 {
29 fRequiresQuoting = TRUE;
30 ++dwMaxEscapedSize;
31 }
32 else if (L'\\' == *pwz) // some backslashes need escaping, so we'll count them all to make sure there is room.
33 {
34 ++dwMaxEscapedSize;
35 }
36
37 ++dwMaxEscapedSize;
38 }
39
40 // If we found anything in the argument that requires our argument to be quoted
41 if (fRequiresQuoting)
42 {
43 hr = StrAlloc(&sczQuotedArg, dwMaxEscapedSize + 3); // plus three for the start and end quote plus null terminator.
44 ExitOnFailure(hr, "Failed to allocate argument to be quoted.");
45
46 LPCWSTR pwz = wzArgument;
47 LPWSTR pwzQuoted = sczQuotedArg;
48
49 *pwzQuoted = L'"';
50 ++pwzQuoted;
51 while (*pwz)
52 {
53 DWORD dwBackslashes = 0;
54 while (L'\\' == *pwz)
55 {
56 ++dwBackslashes;
57 ++pwz;
58 }
59
60 // Escape all backslashes at the end of the string.
61 if (!*pwz)
62 {
63 dwBackslashes *= 2;
64 }
65 else if (L'"' == *pwz) // escape all backslashes before the quote and escape the quote itself.
66 {
67 dwBackslashes = dwBackslashes * 2 + 1;
68 }
69 // the backslashes don't have to be escaped.
70
71 // Add the appropriate number of backslashes
72 for (DWORD i = 0; i < dwBackslashes; ++i)
73 {
74 *pwzQuoted = L'\\';
75 ++pwzQuoted;
76 }
77
78 // If there is a character, add it after all the escaped backslashes
79 if (*pwz)
80 {
81 *pwzQuoted = *pwz;
82 ++pwz;
83 ++pwzQuoted;
84 }
85 }
86
87 *pwzQuoted = L'"';
88 ++pwzQuoted;
89 *pwzQuoted = L'\0'; // ensure the arg is null terminated.
90 }
91
92 // If there is already data in the command line, append a space before appending the
93 // argument.
94 if (*psczCommandLine && **psczCommandLine)
95 {
96 hr = StrAllocConcat(psczCommandLine, L" ", 0);
97 ExitOnFailure(hr, "Failed to append space to command line with existing data.");
98 }
99
100 hr = StrAllocConcat(psczCommandLine, sczQuotedArg ? sczQuotedArg : wzArgument, 0);
101 ExitOnFailure(hr, "Failed to copy command line argument.");
102
103LExit:
104 ReleaseStr(sczQuotedArg);
105
106 return hr;
107}
108
109
110DAPI_(LPWSTR) PathFile(
111 __in_z LPCWSTR wzPath
112 )
113{
114 if (!wzPath)
115 {
116 return NULL;
117 }
118
119 LPWSTR wzFile = const_cast<LPWSTR>(wzPath);
120 for (LPWSTR wz = wzFile; *wz; ++wz)
121 {
122 // valid delineators
123 // \ => Windows path
124 // / => unix and URL path
125 // : => relative path from mapped root
126 if (L'\\' == *wz || L'/' == *wz || (L':' == *wz && wz == wzPath + 1))
127 {
128 wzFile = wz + 1;
129 }
130 }
131
132 return wzFile;
133}
134
135
136DAPI_(LPCWSTR) PathExtension(
137 __in_z LPCWSTR wzPath
138 )
139{
140 if (!wzPath)
141 {
142 return NULL;
143 }
144
145 // Find the last dot in the last thing that could be a file.
146 LPCWSTR wzExtension = NULL;
147 for (LPCWSTR wz = wzPath; *wz; ++wz)
148 {
149 if (L'\\' == *wz || L'/' == *wz || L':' == *wz)
150 {
151 wzExtension = NULL;
152 }
153 else if (L'.' == *wz)
154 {
155 wzExtension = wz;
156 }
157 }
158
159 return wzExtension;
160}
161
162
163DAPI_(HRESULT) PathGetDirectory(
164 __in_z LPCWSTR wzPath,
165 __out LPWSTR *psczDirectory
166 )
167{
168 HRESULT hr = S_OK;
169 DWORD cchDirectory = DWORD_MAX;
170
171 for (LPCWSTR wz = wzPath; *wz; ++wz)
172 {
173 // valid delineators:
174 // \ => Windows path
175 // / => unix and URL path
176 // : => relative path from mapped root
177 if (L'\\' == *wz || L'/' == *wz || (L':' == *wz && wz == wzPath + 1))
178 {
179 cchDirectory = static_cast<DWORD>(wz - wzPath) + 1;
180 }
181 }
182
183 if (DWORD_MAX == cchDirectory)
184 {
185 // we were given just a file name, so there's no directory available
186 return S_FALSE;
187 }
188
189 if (wzPath[0] == L'\"')
190 {
191 ++wzPath;
192 --cchDirectory;
193 }
194
195 hr = StrAllocString(psczDirectory, wzPath, cchDirectory);
196 ExitOnFailure(hr, "Failed to copy directory.");
197
198LExit:
199 return hr;
200}
201
202
203DAPI_(HRESULT) PathExpand(
204 __out LPWSTR *psczFullPath,
205 __in_z LPCWSTR wzRelativePath,
206 __in DWORD dwResolveFlags
207 )
208{
209 Assert(wzRelativePath && *wzRelativePath);
210
211 HRESULT hr = S_OK;
212 DWORD cch = 0;
213 LPWSTR sczExpandedPath = NULL;
214 DWORD cchExpandedPath = 0;
215
216 LPWSTR sczFullPath = NULL;
217
218 //
219 // First, expand any environment variables.
220 //
221 if (dwResolveFlags & PATH_EXPAND_ENVIRONMENT)
222 {
223 cchExpandedPath = PATH_GOOD_ENOUGH;
224
225 hr = StrAlloc(&sczExpandedPath, cchExpandedPath);
226 ExitOnFailure(hr, "Failed to allocate space for expanded path.");
227
228 cch = ::ExpandEnvironmentStringsW(wzRelativePath, sczExpandedPath, cchExpandedPath);
229 if (0 == cch)
230 {
231 ExitWithLastError(hr, "Failed to expand environment variables in string: %ls", wzRelativePath);
232 }
233 else if (cchExpandedPath < cch)
234 {
235 cchExpandedPath = cch;
236 hr = StrAlloc(&sczExpandedPath, cchExpandedPath);
237 ExitOnFailure(hr, "Failed to re-allocate more space for expanded path.");
238
239 cch = ::ExpandEnvironmentStringsW(wzRelativePath, sczExpandedPath, cchExpandedPath);
240 if (0 == cch)
241 {
242 ExitWithLastError(hr, "Failed to expand environment variables in string: %ls", wzRelativePath);
243 }
244 else if (cchExpandedPath < cch)
245 {
246 hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
247 ExitOnRootFailure(hr, "Failed to allocate buffer for expanded path.");
248 }
249 }
250
251 if (MAX_PATH < cch)
252 {
253 hr = PathPrefix(&sczExpandedPath); // ignore invald arg from path prefix because this may not be a complete path yet
254 if (E_INVALIDARG == hr)
255 {
256 hr = S_OK;
257 }
258 ExitOnFailure(hr, "Failed to prefix long path after expanding environment variables.");
259
260 hr = StrMaxLength(sczExpandedPath, reinterpret_cast<DWORD_PTR *>(&cchExpandedPath));
261 ExitOnFailure(hr, "Failed to get max length of expanded path.");
262 }
263 }
264
265 //
266 // Second, get the full path.
267 //
268 if (dwResolveFlags & PATH_EXPAND_FULLPATH)
269 {
270 LPWSTR wzFileName = NULL;
271 LPCWSTR wzPath = sczExpandedPath ? sczExpandedPath : wzRelativePath;
272 DWORD cchFullPath = PATH_GOOD_ENOUGH < cchExpandedPath ? cchExpandedPath : PATH_GOOD_ENOUGH;
273
274 hr = StrAlloc(&sczFullPath, cchFullPath);
275 ExitOnFailure(hr, "Failed to allocate space for full path.");
276
277 cch = ::GetFullPathNameW(wzPath, cchFullPath, sczFullPath, &wzFileName);
278 if (0 == cch)
279 {
280 ExitWithLastError(hr, "Failed to get full path for string: %ls", wzPath);
281 }
282 else if (cchFullPath < cch)
283 {
284 cchFullPath = cch < MAX_PATH ? cch : cch + 7; // ensure space for "\\?\UNC" prefix if needed
285 hr = StrAlloc(&sczFullPath, cchFullPath);
286 ExitOnFailure(hr, "Failed to re-allocate more space for full path.");
287
288 cch = ::GetFullPathNameW(wzPath, cchFullPath, sczFullPath, &wzFileName);
289 if (0 == cch)
290 {
291 ExitWithLastError(hr, "Failed to get full path for string: %ls", wzPath);
292 }
293 else if (cchFullPath < cch)
294 {
295 hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
296 ExitOnRootFailure(hr, "Failed to allocate buffer for full path.");
297 }
298 }
299
300 if (MAX_PATH < cch)
301 {
302 hr = PathPrefix(&sczFullPath);
303 ExitOnFailure(hr, "Failed to prefix long path after expanding.");
304 }
305 }
306 else
307 {
308 sczFullPath = sczExpandedPath;
309 sczExpandedPath = NULL;
310 }
311
312 hr = StrAllocString(psczFullPath, sczFullPath? sczFullPath : wzRelativePath, 0);
313 ExitOnFailure(hr, "Failed to copy relative path into full path.");
314
315LExit:
316 ReleaseStr(sczFullPath);
317 ReleaseStr(sczExpandedPath);
318
319 return hr;
320}
321
322
323DAPI_(HRESULT) PathPrefix(
324 __inout LPWSTR *psczFullPath
325 )
326{
327 Assert(psczFullPath && *psczFullPath);
328
329 HRESULT hr = S_OK;
330 LPWSTR wzFullPath = *psczFullPath;
331 DWORD_PTR cbFullPath = 0;
332
333 if (((L'a' <= wzFullPath[0] && L'z' >= wzFullPath[0]) ||
334 (L'A' <= wzFullPath[0] && L'Z' >= wzFullPath[0])) &&
335 L':' == wzFullPath[1] &&
336 L'\\' == wzFullPath[2]) // normal path
337 {
338 hr = StrAllocPrefix(psczFullPath, L"\\\\?\\", 4);
339 ExitOnFailure(hr, "Failed to add prefix to file path.");
340 }
341 else if (L'\\' == wzFullPath[0] && L'\\' == wzFullPath[1]) // UNC
342 {
343 // ensure that we're not already prefixed
344 if (!(L'?' == wzFullPath[2] && L'\\' == wzFullPath[3]))
345 {
346 hr = StrSize(*psczFullPath, &cbFullPath);
347 ExitOnFailure(hr, "Failed to get size of full path.");
348
349 memmove_s(wzFullPath, cbFullPath, wzFullPath + 1, cbFullPath - sizeof(WCHAR));
350
351 hr = StrAllocPrefix(psczFullPath, L"\\\\?\\UNC", 7);
352 ExitOnFailure(hr, "Failed to add prefix to UNC path.");
353 }
354 }
355 else
356 {
357 hr = E_INVALIDARG;
358 ExitOnFailure(hr, "Invalid path provided to prefix: %ls.", wzFullPath);
359 }
360
361LExit:
362 return hr;
363}
364
365
366DAPI_(HRESULT) PathFixedBackslashTerminate(
367 __inout_ecount_z(cchPath) LPWSTR wzPath,
368 __in DWORD_PTR cchPath
369 )
370{
371 HRESULT hr = S_OK;
372 size_t cchLength = 0;
373
374 hr = ::StringCchLengthW(wzPath, cchPath, &cchLength);
375 ExitOnFailure(hr, "Failed to get length of path.");
376
377 if (cchLength >= cchPath)
378 {
379 hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
380 }
381 else if (L'\\' != wzPath[cchLength - 1])
382 {
383 wzPath[cchLength] = L'\\';
384 wzPath[cchLength + 1] = L'\0';
385 }
386
387LExit:
388 return hr;
389}
390
391
392DAPI_(HRESULT) PathBackslashTerminate(
393 __inout LPWSTR* psczPath
394 )
395{
396 Assert(psczPath && *psczPath);
397
398 HRESULT hr = S_OK;
399 DWORD_PTR cchPath = 0;
400 size_t cchLength = 0;
401
402 hr = StrMaxLength(*psczPath, &cchPath);
403 ExitOnFailure(hr, "Failed to get size of path string.");
404
405 hr = ::StringCchLengthW(*psczPath, cchPath, &cchLength);
406 ExitOnFailure(hr, "Failed to get length of path.");
407
408 if (L'\\' != (*psczPath)[cchLength - 1])
409 {
410 hr = StrAllocConcat(psczPath, L"\\", 1);
411 ExitOnFailure(hr, "Failed to concat backslash onto string.");
412 }
413
414LExit:
415 return hr;
416}
417
418
419DAPI_(HRESULT) PathForCurrentProcess(
420 __inout LPWSTR *psczFullPath,
421 __in_opt HMODULE hModule
422 )
423{
424 HRESULT hr = S_OK;
425 DWORD cch = MAX_PATH;
426
427 do
428 {
429 hr = StrAlloc(psczFullPath, cch);
430 ExitOnFailure(hr, "Failed to allocate string for module path.");
431
432 DWORD cchRequired = ::GetModuleFileNameW(hModule, *psczFullPath, cch);
433 if (0 == cchRequired)
434 {
435 ExitWithLastError(hr, "Failed to get path for executing process.");
436 }
437 else if (cchRequired == cch)
438 {
439 cch = cchRequired + 1;
440 hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
441 }
442 else
443 {
444 hr = S_OK;
445 }
446 } while (HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) == hr);
447
448LExit:
449 return hr;
450}
451
452
453DAPI_(HRESULT) PathRelativeToModule(
454 __inout LPWSTR *psczFullPath,
455 __in_opt LPCWSTR wzFileName,
456 __in_opt HMODULE hModule
457 )
458{
459 HRESULT hr = PathForCurrentProcess(psczFullPath, hModule);
460 ExitOnFailure(hr, "Failed to get current module path.");
461
462 hr = PathGetDirectory(*psczFullPath, psczFullPath);
463 ExitOnFailure(hr, "Failed to get current module directory.");
464
465 if (wzFileName)
466 {
467 hr = PathConcat(*psczFullPath, wzFileName, psczFullPath);
468 ExitOnFailure(hr, "Failed to append filename.");
469 }
470
471LExit:
472 return hr;
473}
474
475
476DAPI_(HRESULT) PathCreateTempFile(
477 __in_opt LPCWSTR wzDirectory,
478 __in_opt __format_string LPCWSTR wzFileNameTemplate,
479 __in DWORD dwUniqueCount,
480 __in DWORD dwFileAttributes,
481 __out_opt LPWSTR* psczTempFile,
482 __out_opt HANDLE* phTempFile
483 )
484{
485 AssertSz(0 < dwUniqueCount, "Must specify a non-zero unique count.");
486
487 HRESULT hr = S_OK;
488
489 LPWSTR sczTempPath = NULL;
490 DWORD cchTempPath = MAX_PATH;
491
492 HANDLE hTempFile = INVALID_HANDLE_VALUE;
493 LPWSTR scz = NULL;
494 LPWSTR sczTempFile = NULL;
495
496 if (wzDirectory && *wzDirectory)
497 {
498 hr = StrAllocString(&sczTempPath, wzDirectory, 0);
499 ExitOnFailure(hr, "Failed to copy temp path.");
500 }
501 else
502 {
503 hr = StrAlloc(&sczTempPath, cchTempPath);
504 ExitOnFailure(hr, "Failed to allocate memory for the temp path.");
505
506 if (!::GetTempPathW(cchTempPath, sczTempPath))
507 {
508 ExitWithLastError(hr, "Failed to get temp path.");
509 }
510 }
511
512 if (wzFileNameTemplate && *wzFileNameTemplate)
513 {
514 for (DWORD i = 1; i <= dwUniqueCount && INVALID_HANDLE_VALUE == hTempFile; ++i)
515 {
516 hr = StrAllocFormatted(&scz, wzFileNameTemplate, i);
517 ExitOnFailure(hr, "Failed to allocate memory for file template.");
518
519 hr = StrAllocFormatted(&sczTempFile, L"%s%s", sczTempPath, scz);
520 ExitOnFailure(hr, "Failed to allocate temp file name.");
521
522 hTempFile = ::CreateFileW(sczTempFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, CREATE_NEW, dwFileAttributes, NULL);
523 if (INVALID_HANDLE_VALUE == hTempFile)
524 {
525 // if the file already exists, just try again
526 hr = HRESULT_FROM_WIN32(::GetLastError());
527 if (HRESULT_FROM_WIN32(ERROR_FILE_EXISTS) == hr)
528 {
529 hr = S_OK;
530 }
531 ExitOnFailure(hr, "Failed to create file: %ls", sczTempFile);
532 }
533 }
534 }
535
536 // If we were not able to or we did not try to create a temp file, ask
537 // the system to provide us a temp file using its built-in mechanism.
538 if (INVALID_HANDLE_VALUE == hTempFile)
539 {
540 hr = StrAlloc(&sczTempFile, MAX_PATH);
541 ExitOnFailure(hr, "Failed to allocate memory for the temp path");
542
543 if (!::GetTempFileNameW(sczTempPath, L"TMP", 0, sczTempFile))
544 {
545 ExitWithLastError(hr, "Failed to create new temp file name.");
546 }
547
548 hTempFile = ::CreateFileW(sczTempFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, dwFileAttributes, NULL);
549 if (INVALID_HANDLE_VALUE == hTempFile)
550 {
551 ExitWithLastError(hr, "Failed to open new temp file: %ls", sczTempFile);
552 }
553 }
554
555 // If the caller wanted the temp file name or handle, return them here.
556 if (psczTempFile)
557 {
558 hr = StrAllocString(psczTempFile, sczTempFile, 0);
559 ExitOnFailure(hr, "Failed to copy temp file string.");
560 }
561
562 if (phTempFile)
563 {
564 *phTempFile = hTempFile;
565 hTempFile = INVALID_HANDLE_VALUE;
566 }
567
568LExit:
569 if (INVALID_HANDLE_VALUE != hTempFile)
570 {
571 ::CloseHandle(hTempFile);
572 }
573
574 ReleaseStr(scz);
575 ReleaseStr(sczTempFile);
576 ReleaseStr(sczTempPath);
577
578 return hr;
579}
580
581
582DAPI_(HRESULT) PathCreateTimeBasedTempFile(
583 __in_z_opt LPCWSTR wzDirectory,
584 __in_z LPCWSTR wzPrefix,
585 __in_z_opt LPCWSTR wzPostfix,
586 __in_z LPCWSTR wzExtension,
587 __deref_opt_out_z LPWSTR* psczTempFile,
588 __out_opt HANDLE* phTempFile
589 )
590{
591 HRESULT hr = S_OK;
592 BOOL fRetry = FALSE;
593 WCHAR wzTempPath[MAX_PATH] = { };
594 LPWSTR sczPrefix = NULL;
595 LPWSTR sczPrefixFolder = NULL;
596 SYSTEMTIME time = { };
597
598 LPWSTR sczTempPath = NULL;
599 HANDLE hTempFile = INVALID_HANDLE_VALUE;
600 DWORD dwAttempts = 0;
601
602 if (wzDirectory && *wzDirectory)
603 {
604 hr = PathConcat(wzDirectory, wzPrefix, &sczPrefix);
605 ExitOnFailure(hr, "Failed to combine directory and log prefix.");
606 }
607 else
608 {
609 if (!::GetTempPathW(countof(wzTempPath), wzTempPath))
610 {
611 ExitWithLastError(hr, "Failed to get temp folder.");
612 }
613
614 hr = PathConcat(wzTempPath, wzPrefix, &sczPrefix);
615 ExitOnFailure(hr, "Failed to concatenate the temp folder and log prefix.");
616 }
617
618 hr = PathGetDirectory(sczPrefix, &sczPrefixFolder);
619 if (S_OK == hr)
620 {
621 hr = DirEnsureExists(sczPrefixFolder, NULL);
622 ExitOnFailure(hr, "Failed to ensure temp file path exists: %ls", sczPrefixFolder);
623 }
624
625 if (!wzPostfix)
626 {
627 wzPostfix = L"";
628 }
629
630 do
631 {
632 fRetry = FALSE;
633 ++dwAttempts;
634
635 ::GetLocalTime(&time);
636
637 // Log format: pre YYYY MM dd hh mm ss post ext
638 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);
639 ExitOnFailure(hr, "failed to allocate memory for the temp path");
640
641 hTempFile = ::CreateFileW(sczTempPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
642 if (INVALID_HANDLE_VALUE == hTempFile)
643 {
644 // If the file already exists, just try again.
645 DWORD er = ::GetLastError();
646 if (ERROR_FILE_EXISTS == er || ERROR_ACCESS_DENIED == er)
647 {
648 ::Sleep(100);
649
650 if (10 > dwAttempts)
651 {
652 er = ERROR_SUCCESS;
653 fRetry = TRUE;
654 }
655 }
656
657 hr = HRESULT_FROM_WIN32(er);
658 ExitOnFailureDebugTrace(hr, "Failed to create temp file: %ls", sczTempPath);
659 }
660 } while (fRetry);
661
662 if (psczTempFile)
663 {
664 hr = StrAllocString(psczTempFile, sczTempPath, 0);
665 ExitOnFailure(hr, "Failed to copy temp path to return.");
666 }
667
668 if (phTempFile)
669 {
670 *phTempFile = hTempFile;
671 hTempFile = INVALID_HANDLE_VALUE;
672 }
673
674LExit:
675 ReleaseFile(hTempFile);
676 ReleaseStr(sczTempPath);
677 ReleaseStr(sczPrefixFolder);
678 ReleaseStr(sczPrefix);
679
680 return hr;
681}
682
683
684DAPI_(HRESULT) PathCreateTempDirectory(
685 __in_opt LPCWSTR wzDirectory,
686 __in __format_string LPCWSTR wzDirectoryNameTemplate,
687 __in DWORD dwUniqueCount,
688 __out LPWSTR* psczTempDirectory
689 )
690{
691 AssertSz(wzDirectoryNameTemplate && *wzDirectoryNameTemplate, "DirectoryNameTemplate must be specified.");
692 AssertSz(0 < dwUniqueCount, "Must specify a non-zero unique count.");
693
694 HRESULT hr = S_OK;
695
696 LPWSTR sczTempPath = NULL;
697 DWORD cchTempPath = MAX_PATH;
698
699 LPWSTR scz = NULL;
700
701 if (wzDirectory && *wzDirectory)
702 {
703 hr = StrAllocString(&sczTempPath, wzDirectory, 0);
704 ExitOnFailure(hr, "Failed to copy temp path.");
705
706 hr = PathBackslashTerminate(&sczTempPath);
707 ExitOnFailure(hr, "Failed to ensure path ends in backslash: %ls", wzDirectory);
708 }
709 else
710 {
711 hr = StrAlloc(&sczTempPath, cchTempPath);
712 ExitOnFailure(hr, "Failed to allocate memory for the temp path.");
713
714 if (!::GetTempPathW(cchTempPath, sczTempPath))
715 {
716 ExitWithLastError(hr, "Failed to get temp path.");
717 }
718 }
719
720 for (DWORD i = 1; i <= dwUniqueCount; ++i)
721 {
722 hr = StrAllocFormatted(&scz, wzDirectoryNameTemplate, i);
723 ExitOnFailure(hr, "Failed to allocate memory for directory name template.");
724
725 hr = StrAllocFormatted(psczTempDirectory, L"%s%s", sczTempPath, scz);
726 ExitOnFailure(hr, "Failed to allocate temp directory name.");
727
728 if (!::CreateDirectoryW(*psczTempDirectory, NULL))
729 {
730 DWORD er = ::GetLastError();
731 if (ERROR_ALREADY_EXISTS == er)
732 {
733 hr = HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS);
734 continue;
735 }
736 else if (ERROR_PATH_NOT_FOUND == er)
737 {
738 hr = DirEnsureExists(*psczTempDirectory, NULL);
739 break;
740 }
741 else
742 {
743 hr = HRESULT_FROM_WIN32(er);
744 break;
745 }
746 }
747 else
748 {
749 hr = S_OK;
750 break;
751 }
752 }
753 ExitOnFailure(hr, "Failed to create temp directory.");
754
755 hr = PathBackslashTerminate(psczTempDirectory);
756 ExitOnFailure(hr, "Failed to ensure temp directory is backslash terminated.");
757
758LExit:
759 ReleaseStr(scz);
760 ReleaseStr(sczTempPath);
761
762 return hr;
763}
764
765
766DAPI_(HRESULT) PathGetKnownFolder(
767 __in int csidl,
768 __out LPWSTR* psczKnownFolder
769 )
770{
771 HRESULT hr = S_OK;
772
773 hr = StrAlloc(psczKnownFolder, MAX_PATH);
774 ExitOnFailure(hr, "Failed to allocate memory for known folder.");
775
776 hr = ::SHGetFolderPathW(NULL, csidl, NULL, SHGFP_TYPE_CURRENT, *psczKnownFolder);
777 ExitOnFailure(hr, "Failed to get known folder path.");
778
779 hr = PathBackslashTerminate(psczKnownFolder);
780 ExitOnFailure(hr, "Failed to ensure known folder path is backslash terminated.");
781
782LExit:
783 return hr;
784}
785
786
787DAPI_(BOOL) PathIsAbsolute(
788 __in_z LPCWSTR wzPath
789 )
790{
791 DWORD dwLength = lstrlenW(wzPath);
792 return (1 < dwLength) && (wzPath[0] == L'\\') || (wzPath[1] == L':');
793}
794
795
796DAPI_(HRESULT) PathConcat(
797 __in_opt LPCWSTR wzPath1,
798 __in_opt LPCWSTR wzPath2,
799 __deref_out_z LPWSTR* psczCombined
800 )
801{
802 HRESULT hr = S_OK;
803
804 if (!wzPath2 || !*wzPath2)
805 {
806 hr = StrAllocString(psczCombined, wzPath1, 0);
807 ExitOnFailure(hr, "Failed to copy just path1 to output.");
808 }
809 else if (!wzPath1 || !*wzPath1 || PathIsAbsolute(wzPath2))
810 {
811 hr = StrAllocString(psczCombined, wzPath2, 0);
812 ExitOnFailure(hr, "Failed to copy just path2 to output.");
813 }
814 else
815 {
816 hr = StrAllocString(psczCombined, wzPath1, 0);
817 ExitOnFailure(hr, "Failed to copy path1 to output.");
818
819 hr = PathBackslashTerminate(psczCombined);
820 ExitOnFailure(hr, "Failed to backslashify.");
821
822 hr = StrAllocConcat(psczCombined, wzPath2, 0);
823 ExitOnFailure(hr, "Failed to append path2 to output.");
824 }
825
826LExit:
827 return hr;
828}
829
830
831DAPI_(HRESULT) PathEnsureQuoted(
832 __inout LPWSTR* ppszPath,
833 __in BOOL fDirectory
834 )
835{
836 Assert(ppszPath && *ppszPath);
837
838 HRESULT hr = S_OK;
839 size_t cchPath = 0;
840
841 hr = ::StringCchLengthW(*ppszPath, STRSAFE_MAX_CCH, &cchPath);
842 ExitOnFailure(hr, "Failed to get the length of the path.");
843
844 // Handle simple special cases.
845 if (0 == cchPath || (1 == cchPath && L'"' == (*ppszPath)[0]))
846 {
847 hr = StrAllocString(ppszPath, L"\"\"", 2);
848 ExitOnFailure(hr, "Failed to allocate a quoted empty string.");
849
850 ExitFunction();
851 }
852
853 if (L'"' != (*ppszPath)[0])
854 {
855 hr = StrAllocPrefix(ppszPath, L"\"", 1);
856 ExitOnFailure(hr, "Failed to allocate an opening quote.");
857
858 // Add a char for the opening quote.
859 ++cchPath;
860 }
861
862 if (L'"' != (*ppszPath)[cchPath - 1])
863 {
864 hr = StrAllocConcat(ppszPath, L"\"", 1);
865 ExitOnFailure(hr, "Failed to allocate a closing quote.");
866
867 // Add a char for the closing quote.
868 ++cchPath;
869 }
870
871 if (fDirectory)
872 {
873 if (L'\\' != (*ppszPath)[cchPath - 2])
874 {
875 // Change the last char to a backslash and re-append the closing quote.
876 (*ppszPath)[cchPath - 1] = L'\\';
877
878 hr = StrAllocConcat(ppszPath, L"\"", 1);
879 ExitOnFailure(hr, "Failed to allocate another closing quote after the backslash.");
880 }
881 }
882
883LExit:
884
885 return hr;
886}
887
888
889DAPI_(HRESULT) PathCompare(
890 __in_z LPCWSTR wzPath1,
891 __in_z LPCWSTR wzPath2,
892 __out int* pnResult
893 )
894{
895 HRESULT hr = S_OK;
896 LPWSTR sczPath1 = NULL;
897 LPWSTR sczPath2 = NULL;
898
899 hr = PathExpand(&sczPath1, wzPath1, PATH_EXPAND_ENVIRONMENT | PATH_EXPAND_FULLPATH);
900 ExitOnFailure(hr, "Failed to expand path1.");
901
902 hr = PathExpand(&sczPath2, wzPath2, PATH_EXPAND_ENVIRONMENT | PATH_EXPAND_FULLPATH);
903 ExitOnFailure(hr, "Failed to expand path2.");
904
905 *pnResult = ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, sczPath1, -1, sczPath2, -1);
906
907LExit:
908 ReleaseStr(sczPath2);
909 ReleaseStr(sczPath1);
910
911 return hr;
912}
913
914
915DAPI_(HRESULT) PathCompress(
916 __in_z LPCWSTR wzPath
917 )
918{
919 HRESULT hr = S_OK;
920 HANDLE hPath = INVALID_HANDLE_VALUE;
921
922 hPath = ::CreateFileW(wzPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
923 if (INVALID_HANDLE_VALUE == hPath)
924 {
925 ExitWithLastError(hr, "Failed to open path %ls for compression.", wzPath);
926 }
927
928 DWORD dwBytesReturned = 0;
929 USHORT usCompressionFormat = COMPRESSION_FORMAT_DEFAULT;
930 if (0 == ::DeviceIoControl(hPath, FSCTL_SET_COMPRESSION, &usCompressionFormat, sizeof(usCompressionFormat), NULL, 0, &dwBytesReturned, NULL))
931 {
932 // ignore compression attempts on file systems that don't support it
933 DWORD er = ::GetLastError();
934 if (ERROR_INVALID_FUNCTION != er)
935 {
936 ExitOnWin32Error(er, hr, "Failed to set compression state for path %ls.", wzPath);
937 }
938 }
939
940LExit:
941 ReleaseFile(hPath);
942
943 return hr;
944}
945
946DAPI_(HRESULT) PathGetHierarchyArray(
947 __in_z LPCWSTR wzPath,
948 __deref_inout_ecount_opt(*pcStrArray) LPWSTR **prgsczPathArray,
949 __inout LPUINT pcPathArray
950 )
951{
952 HRESULT hr = S_OK;
953 LPWSTR sczPathCopy = NULL;
954 LPWSTR sczNewPathCopy = NULL;
955 DWORD cArraySpacesNeeded = 0;
956
957 for (int i = 0; i < lstrlenW(wzPath); ++i)
958 {
959 if (wzPath[i] == L'\\')
960 {
961 ++cArraySpacesNeeded;
962 }
963 }
964 if (wzPath[lstrlenW(wzPath) - 1] != L'\\')
965 {
966 ++cArraySpacesNeeded;
967 }
968
969 // If it's a UNC path, cut off the first three paths, 2 because it starts with a double backslash, and another because the first ("\\servername\") isn't a path.
970 if (wzPath[0] == L'\\' && wzPath[1] == L'\\')
971 {
972 cArraySpacesNeeded -= 3;
973 }
974
975 Assert(cArraySpacesNeeded >= 1);
976
977 hr = MemEnsureArraySize(reinterpret_cast<void **>(prgsczPathArray), cArraySpacesNeeded, sizeof(LPWSTR), 0);
978 ExitOnFailure(hr, "Failed to allocate array of size %u for parent directories", cArraySpacesNeeded);
979 *pcPathArray = cArraySpacesNeeded;
980
981 hr = StrAllocString(&sczPathCopy, wzPath, 0);
982 ExitOnFailure(hr, "Failed to allocate copy of original path");
983
984 for (DWORD i = 0; i < cArraySpacesNeeded; ++i)
985 {
986 hr = StrAllocString((*prgsczPathArray) + cArraySpacesNeeded - 1 - i, sczPathCopy, 0);
987 ExitOnFailure(hr, "Failed to copy path");
988
989 // If it ends in a backslash, it's a directory path, so cut off everything the last backslash before we get the directory portion of the path
990 if (wzPath[lstrlenW(sczPathCopy) - 1] == L'\\')
991 {
992 sczPathCopy[lstrlenW(sczPathCopy) - 1] = L'\0';
993 }
994
995 hr = PathGetDirectory(sczPathCopy, &sczNewPathCopy);
996 ExitOnFailure(hr, "Failed to get directory portion of path");
997
998 ReleaseStr(sczPathCopy);
999 sczPathCopy = sczNewPathCopy;
1000 sczNewPathCopy = NULL;
1001 }
1002
1003 hr = S_OK;
1004
1005LExit:
1006 ReleaseStr(sczPathCopy);
1007
1008 return hr;
1009}