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