From 5d8375007754101ff2889d0e79486c8f9b7cf5ab Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sun, 3 Sep 2017 11:22:38 -0700 Subject: Initial commit --- src/dutil/pathutil.cpp | 1009 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1009 insertions(+) create mode 100644 src/dutil/pathutil.cpp (limited to 'src/dutil/pathutil.cpp') 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 @@ +// 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. + +#include "precomp.h" + +#define PATH_GOOD_ENOUGH 64 + + +DAPI_(HRESULT) PathCommandLineAppend( + __deref_out_z LPWSTR* psczCommandLine, + __in_z LPCWSTR wzArgument + ) +{ + HRESULT hr = S_OK; + LPWSTR sczQuotedArg = NULL; + BOOL fRequiresQuoting = FALSE; + DWORD dwMaxEscapedSize = 0; + + // Loop through the argument determining if it needs to be quoted and what the maximum + // size would be if there are escape characters required. + for (LPCWSTR pwz = wzArgument; *pwz; ++pwz) + { + // Arguments with whitespace need quoting. + if (L' ' == *pwz || L'\t' == *pwz || L'\n' == *pwz || L'\v' == *pwz) + { + fRequiresQuoting = TRUE; + } + else if (L'"' == *pwz) // quotes need quoting and sometimes escaping. + { + fRequiresQuoting = TRUE; + ++dwMaxEscapedSize; + } + else if (L'\\' == *pwz) // some backslashes need escaping, so we'll count them all to make sure there is room. + { + ++dwMaxEscapedSize; + } + + ++dwMaxEscapedSize; + } + + // If we found anything in the argument that requires our argument to be quoted + if (fRequiresQuoting) + { + hr = StrAlloc(&sczQuotedArg, dwMaxEscapedSize + 3); // plus three for the start and end quote plus null terminator. + ExitOnFailure(hr, "Failed to allocate argument to be quoted."); + + LPCWSTR pwz = wzArgument; + LPWSTR pwzQuoted = sczQuotedArg; + + *pwzQuoted = L'"'; + ++pwzQuoted; + while (*pwz) + { + DWORD dwBackslashes = 0; + while (L'\\' == *pwz) + { + ++dwBackslashes; + ++pwz; + } + + // Escape all backslashes at the end of the string. + if (!*pwz) + { + dwBackslashes *= 2; + } + else if (L'"' == *pwz) // escape all backslashes before the quote and escape the quote itself. + { + dwBackslashes = dwBackslashes * 2 + 1; + } + // the backslashes don't have to be escaped. + + // Add the appropriate number of backslashes + for (DWORD i = 0; i < dwBackslashes; ++i) + { + *pwzQuoted = L'\\'; + ++pwzQuoted; + } + + // If there is a character, add it after all the escaped backslashes + if (*pwz) + { + *pwzQuoted = *pwz; + ++pwz; + ++pwzQuoted; + } + } + + *pwzQuoted = L'"'; + ++pwzQuoted; + *pwzQuoted = L'\0'; // ensure the arg is null terminated. + } + + // If there is already data in the command line, append a space before appending the + // argument. + if (*psczCommandLine && **psczCommandLine) + { + hr = StrAllocConcat(psczCommandLine, L" ", 0); + ExitOnFailure(hr, "Failed to append space to command line with existing data."); + } + + hr = StrAllocConcat(psczCommandLine, sczQuotedArg ? sczQuotedArg : wzArgument, 0); + ExitOnFailure(hr, "Failed to copy command line argument."); + +LExit: + ReleaseStr(sczQuotedArg); + + return hr; +} + + +DAPI_(LPWSTR) PathFile( + __in_z LPCWSTR wzPath + ) +{ + if (!wzPath) + { + return NULL; + } + + LPWSTR wzFile = const_cast(wzPath); + for (LPWSTR wz = wzFile; *wz; ++wz) + { + // valid delineators + // \ => Windows path + // / => unix and URL path + // : => relative path from mapped root + if (L'\\' == *wz || L'/' == *wz || (L':' == *wz && wz == wzPath + 1)) + { + wzFile = wz + 1; + } + } + + return wzFile; +} + + +DAPI_(LPCWSTR) PathExtension( + __in_z LPCWSTR wzPath + ) +{ + if (!wzPath) + { + return NULL; + } + + // Find the last dot in the last thing that could be a file. + LPCWSTR wzExtension = NULL; + for (LPCWSTR wz = wzPath; *wz; ++wz) + { + if (L'\\' == *wz || L'/' == *wz || L':' == *wz) + { + wzExtension = NULL; + } + else if (L'.' == *wz) + { + wzExtension = wz; + } + } + + return wzExtension; +} + + +DAPI_(HRESULT) PathGetDirectory( + __in_z LPCWSTR wzPath, + __out LPWSTR *psczDirectory + ) +{ + HRESULT hr = S_OK; + DWORD cchDirectory = DWORD_MAX; + + for (LPCWSTR wz = wzPath; *wz; ++wz) + { + // valid delineators: + // \ => Windows path + // / => unix and URL path + // : => relative path from mapped root + if (L'\\' == *wz || L'/' == *wz || (L':' == *wz && wz == wzPath + 1)) + { + cchDirectory = static_cast(wz - wzPath) + 1; + } + } + + if (DWORD_MAX == cchDirectory) + { + // we were given just a file name, so there's no directory available + return S_FALSE; + } + + if (wzPath[0] == L'\"') + { + ++wzPath; + --cchDirectory; + } + + hr = StrAllocString(psczDirectory, wzPath, cchDirectory); + ExitOnFailure(hr, "Failed to copy directory."); + +LExit: + return hr; +} + + +DAPI_(HRESULT) PathExpand( + __out LPWSTR *psczFullPath, + __in_z LPCWSTR wzRelativePath, + __in DWORD dwResolveFlags + ) +{ + Assert(wzRelativePath && *wzRelativePath); + + HRESULT hr = S_OK; + DWORD cch = 0; + LPWSTR sczExpandedPath = NULL; + DWORD cchExpandedPath = 0; + + LPWSTR sczFullPath = NULL; + + // + // First, expand any environment variables. + // + if (dwResolveFlags & PATH_EXPAND_ENVIRONMENT) + { + cchExpandedPath = PATH_GOOD_ENOUGH; + + hr = StrAlloc(&sczExpandedPath, cchExpandedPath); + ExitOnFailure(hr, "Failed to allocate space for expanded path."); + + cch = ::ExpandEnvironmentStringsW(wzRelativePath, sczExpandedPath, cchExpandedPath); + if (0 == cch) + { + ExitWithLastError(hr, "Failed to expand environment variables in string: %ls", wzRelativePath); + } + else if (cchExpandedPath < cch) + { + cchExpandedPath = cch; + hr = StrAlloc(&sczExpandedPath, cchExpandedPath); + ExitOnFailure(hr, "Failed to re-allocate more space for expanded path."); + + cch = ::ExpandEnvironmentStringsW(wzRelativePath, sczExpandedPath, cchExpandedPath); + if (0 == cch) + { + ExitWithLastError(hr, "Failed to expand environment variables in string: %ls", wzRelativePath); + } + else if (cchExpandedPath < cch) + { + hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + ExitOnRootFailure(hr, "Failed to allocate buffer for expanded path."); + } + } + + if (MAX_PATH < cch) + { + hr = PathPrefix(&sczExpandedPath); // ignore invald arg from path prefix because this may not be a complete path yet + if (E_INVALIDARG == hr) + { + hr = S_OK; + } + ExitOnFailure(hr, "Failed to prefix long path after expanding environment variables."); + + hr = StrMaxLength(sczExpandedPath, reinterpret_cast(&cchExpandedPath)); + ExitOnFailure(hr, "Failed to get max length of expanded path."); + } + } + + // + // Second, get the full path. + // + if (dwResolveFlags & PATH_EXPAND_FULLPATH) + { + LPWSTR wzFileName = NULL; + LPCWSTR wzPath = sczExpandedPath ? sczExpandedPath : wzRelativePath; + DWORD cchFullPath = PATH_GOOD_ENOUGH < cchExpandedPath ? cchExpandedPath : PATH_GOOD_ENOUGH; + + hr = StrAlloc(&sczFullPath, cchFullPath); + ExitOnFailure(hr, "Failed to allocate space for full path."); + + cch = ::GetFullPathNameW(wzPath, cchFullPath, sczFullPath, &wzFileName); + if (0 == cch) + { + ExitWithLastError(hr, "Failed to get full path for string: %ls", wzPath); + } + else if (cchFullPath < cch) + { + cchFullPath = cch < MAX_PATH ? cch : cch + 7; // ensure space for "\\?\UNC" prefix if needed + hr = StrAlloc(&sczFullPath, cchFullPath); + ExitOnFailure(hr, "Failed to re-allocate more space for full path."); + + cch = ::GetFullPathNameW(wzPath, cchFullPath, sczFullPath, &wzFileName); + if (0 == cch) + { + ExitWithLastError(hr, "Failed to get full path for string: %ls", wzPath); + } + else if (cchFullPath < cch) + { + hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + ExitOnRootFailure(hr, "Failed to allocate buffer for full path."); + } + } + + if (MAX_PATH < cch) + { + hr = PathPrefix(&sczFullPath); + ExitOnFailure(hr, "Failed to prefix long path after expanding."); + } + } + else + { + sczFullPath = sczExpandedPath; + sczExpandedPath = NULL; + } + + hr = StrAllocString(psczFullPath, sczFullPath? sczFullPath : wzRelativePath, 0); + ExitOnFailure(hr, "Failed to copy relative path into full path."); + +LExit: + ReleaseStr(sczFullPath); + ReleaseStr(sczExpandedPath); + + return hr; +} + + +DAPI_(HRESULT) PathPrefix( + __inout LPWSTR *psczFullPath + ) +{ + Assert(psczFullPath && *psczFullPath); + + HRESULT hr = S_OK; + LPWSTR wzFullPath = *psczFullPath; + DWORD_PTR cbFullPath = 0; + + if (((L'a' <= wzFullPath[0] && L'z' >= wzFullPath[0]) || + (L'A' <= wzFullPath[0] && L'Z' >= wzFullPath[0])) && + L':' == wzFullPath[1] && + L'\\' == wzFullPath[2]) // normal path + { + hr = StrAllocPrefix(psczFullPath, L"\\\\?\\", 4); + ExitOnFailure(hr, "Failed to add prefix to file path."); + } + else if (L'\\' == wzFullPath[0] && L'\\' == wzFullPath[1]) // UNC + { + // ensure that we're not already prefixed + if (!(L'?' == wzFullPath[2] && L'\\' == wzFullPath[3])) + { + hr = StrSize(*psczFullPath, &cbFullPath); + ExitOnFailure(hr, "Failed to get size of full path."); + + memmove_s(wzFullPath, cbFullPath, wzFullPath + 1, cbFullPath - sizeof(WCHAR)); + + hr = StrAllocPrefix(psczFullPath, L"\\\\?\\UNC", 7); + ExitOnFailure(hr, "Failed to add prefix to UNC path."); + } + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid path provided to prefix: %ls.", wzFullPath); + } + +LExit: + return hr; +} + + +DAPI_(HRESULT) PathFixedBackslashTerminate( + __inout_ecount_z(cchPath) LPWSTR wzPath, + __in DWORD_PTR cchPath + ) +{ + HRESULT hr = S_OK; + size_t cchLength = 0; + + hr = ::StringCchLengthW(wzPath, cchPath, &cchLength); + ExitOnFailure(hr, "Failed to get length of path."); + + if (cchLength >= cchPath) + { + hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + else if (L'\\' != wzPath[cchLength - 1]) + { + wzPath[cchLength] = L'\\'; + wzPath[cchLength + 1] = L'\0'; + } + +LExit: + return hr; +} + + +DAPI_(HRESULT) PathBackslashTerminate( + __inout LPWSTR* psczPath + ) +{ + Assert(psczPath && *psczPath); + + HRESULT hr = S_OK; + DWORD_PTR cchPath = 0; + size_t cchLength = 0; + + hr = StrMaxLength(*psczPath, &cchPath); + ExitOnFailure(hr, "Failed to get size of path string."); + + hr = ::StringCchLengthW(*psczPath, cchPath, &cchLength); + ExitOnFailure(hr, "Failed to get length of path."); + + if (L'\\' != (*psczPath)[cchLength - 1]) + { + hr = StrAllocConcat(psczPath, L"\\", 1); + ExitOnFailure(hr, "Failed to concat backslash onto string."); + } + +LExit: + return hr; +} + + +DAPI_(HRESULT) PathForCurrentProcess( + __inout LPWSTR *psczFullPath, + __in_opt HMODULE hModule + ) +{ + HRESULT hr = S_OK; + DWORD cch = MAX_PATH; + + do + { + hr = StrAlloc(psczFullPath, cch); + ExitOnFailure(hr, "Failed to allocate string for module path."); + + DWORD cchRequired = ::GetModuleFileNameW(hModule, *psczFullPath, cch); + if (0 == cchRequired) + { + ExitWithLastError(hr, "Failed to get path for executing process."); + } + else if (cchRequired == cch) + { + cch = cchRequired + 1; + hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + else + { + hr = S_OK; + } + } while (HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) == hr); + +LExit: + return hr; +} + + +DAPI_(HRESULT) PathRelativeToModule( + __inout LPWSTR *psczFullPath, + __in_opt LPCWSTR wzFileName, + __in_opt HMODULE hModule + ) +{ + HRESULT hr = PathForCurrentProcess(psczFullPath, hModule); + ExitOnFailure(hr, "Failed to get current module path."); + + hr = PathGetDirectory(*psczFullPath, psczFullPath); + ExitOnFailure(hr, "Failed to get current module directory."); + + if (wzFileName) + { + hr = PathConcat(*psczFullPath, wzFileName, psczFullPath); + ExitOnFailure(hr, "Failed to append filename."); + } + +LExit: + return hr; +} + + +DAPI_(HRESULT) PathCreateTempFile( + __in_opt LPCWSTR wzDirectory, + __in_opt __format_string LPCWSTR wzFileNameTemplate, + __in DWORD dwUniqueCount, + __in DWORD dwFileAttributes, + __out_opt LPWSTR* psczTempFile, + __out_opt HANDLE* phTempFile + ) +{ + AssertSz(0 < dwUniqueCount, "Must specify a non-zero unique count."); + + HRESULT hr = S_OK; + + LPWSTR sczTempPath = NULL; + DWORD cchTempPath = MAX_PATH; + + HANDLE hTempFile = INVALID_HANDLE_VALUE; + LPWSTR scz = NULL; + LPWSTR sczTempFile = NULL; + + if (wzDirectory && *wzDirectory) + { + hr = StrAllocString(&sczTempPath, wzDirectory, 0); + ExitOnFailure(hr, "Failed to copy temp path."); + } + else + { + hr = StrAlloc(&sczTempPath, cchTempPath); + ExitOnFailure(hr, "Failed to allocate memory for the temp path."); + + if (!::GetTempPathW(cchTempPath, sczTempPath)) + { + ExitWithLastError(hr, "Failed to get temp path."); + } + } + + if (wzFileNameTemplate && *wzFileNameTemplate) + { + for (DWORD i = 1; i <= dwUniqueCount && INVALID_HANDLE_VALUE == hTempFile; ++i) + { + hr = StrAllocFormatted(&scz, wzFileNameTemplate, i); + ExitOnFailure(hr, "Failed to allocate memory for file template."); + + hr = StrAllocFormatted(&sczTempFile, L"%s%s", sczTempPath, scz); + ExitOnFailure(hr, "Failed to allocate temp file name."); + + hTempFile = ::CreateFileW(sczTempFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, CREATE_NEW, dwFileAttributes, NULL); + if (INVALID_HANDLE_VALUE == hTempFile) + { + // if the file already exists, just try again + hr = HRESULT_FROM_WIN32(::GetLastError()); + if (HRESULT_FROM_WIN32(ERROR_FILE_EXISTS) == hr) + { + hr = S_OK; + } + ExitOnFailure(hr, "Failed to create file: %ls", sczTempFile); + } + } + } + + // If we were not able to or we did not try to create a temp file, ask + // the system to provide us a temp file using its built-in mechanism. + if (INVALID_HANDLE_VALUE == hTempFile) + { + hr = StrAlloc(&sczTempFile, MAX_PATH); + ExitOnFailure(hr, "Failed to allocate memory for the temp path"); + + if (!::GetTempFileNameW(sczTempPath, L"TMP", 0, sczTempFile)) + { + ExitWithLastError(hr, "Failed to create new temp file name."); + } + + hTempFile = ::CreateFileW(sczTempFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, dwFileAttributes, NULL); + if (INVALID_HANDLE_VALUE == hTempFile) + { + ExitWithLastError(hr, "Failed to open new temp file: %ls", sczTempFile); + } + } + + // If the caller wanted the temp file name or handle, return them here. + if (psczTempFile) + { + hr = StrAllocString(psczTempFile, sczTempFile, 0); + ExitOnFailure(hr, "Failed to copy temp file string."); + } + + if (phTempFile) + { + *phTempFile = hTempFile; + hTempFile = INVALID_HANDLE_VALUE; + } + +LExit: + if (INVALID_HANDLE_VALUE != hTempFile) + { + ::CloseHandle(hTempFile); + } + + ReleaseStr(scz); + ReleaseStr(sczTempFile); + ReleaseStr(sczTempPath); + + return hr; +} + + +DAPI_(HRESULT) PathCreateTimeBasedTempFile( + __in_z_opt LPCWSTR wzDirectory, + __in_z LPCWSTR wzPrefix, + __in_z_opt LPCWSTR wzPostfix, + __in_z LPCWSTR wzExtension, + __deref_opt_out_z LPWSTR* psczTempFile, + __out_opt HANDLE* phTempFile + ) +{ + HRESULT hr = S_OK; + BOOL fRetry = FALSE; + WCHAR wzTempPath[MAX_PATH] = { }; + LPWSTR sczPrefix = NULL; + LPWSTR sczPrefixFolder = NULL; + SYSTEMTIME time = { }; + + LPWSTR sczTempPath = NULL; + HANDLE hTempFile = INVALID_HANDLE_VALUE; + DWORD dwAttempts = 0; + + if (wzDirectory && *wzDirectory) + { + hr = PathConcat(wzDirectory, wzPrefix, &sczPrefix); + ExitOnFailure(hr, "Failed to combine directory and log prefix."); + } + else + { + if (!::GetTempPathW(countof(wzTempPath), wzTempPath)) + { + ExitWithLastError(hr, "Failed to get temp folder."); + } + + hr = PathConcat(wzTempPath, wzPrefix, &sczPrefix); + ExitOnFailure(hr, "Failed to concatenate the temp folder and log prefix."); + } + + hr = PathGetDirectory(sczPrefix, &sczPrefixFolder); + if (S_OK == hr) + { + hr = DirEnsureExists(sczPrefixFolder, NULL); + ExitOnFailure(hr, "Failed to ensure temp file path exists: %ls", sczPrefixFolder); + } + + if (!wzPostfix) + { + wzPostfix = L""; + } + + do + { + fRetry = FALSE; + ++dwAttempts; + + ::GetLocalTime(&time); + + // Log format: pre YYYY MM dd hh mm ss post ext + 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); + ExitOnFailure(hr, "failed to allocate memory for the temp path"); + + hTempFile = ::CreateFileW(sczTempPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); + if (INVALID_HANDLE_VALUE == hTempFile) + { + // If the file already exists, just try again. + DWORD er = ::GetLastError(); + if (ERROR_FILE_EXISTS == er || ERROR_ACCESS_DENIED == er) + { + ::Sleep(100); + + if (10 > dwAttempts) + { + er = ERROR_SUCCESS; + fRetry = TRUE; + } + } + + hr = HRESULT_FROM_WIN32(er); + ExitOnFailureDebugTrace(hr, "Failed to create temp file: %ls", sczTempPath); + } + } while (fRetry); + + if (psczTempFile) + { + hr = StrAllocString(psczTempFile, sczTempPath, 0); + ExitOnFailure(hr, "Failed to copy temp path to return."); + } + + if (phTempFile) + { + *phTempFile = hTempFile; + hTempFile = INVALID_HANDLE_VALUE; + } + +LExit: + ReleaseFile(hTempFile); + ReleaseStr(sczTempPath); + ReleaseStr(sczPrefixFolder); + ReleaseStr(sczPrefix); + + return hr; +} + + +DAPI_(HRESULT) PathCreateTempDirectory( + __in_opt LPCWSTR wzDirectory, + __in __format_string LPCWSTR wzDirectoryNameTemplate, + __in DWORD dwUniqueCount, + __out LPWSTR* psczTempDirectory + ) +{ + AssertSz(wzDirectoryNameTemplate && *wzDirectoryNameTemplate, "DirectoryNameTemplate must be specified."); + AssertSz(0 < dwUniqueCount, "Must specify a non-zero unique count."); + + HRESULT hr = S_OK; + + LPWSTR sczTempPath = NULL; + DWORD cchTempPath = MAX_PATH; + + LPWSTR scz = NULL; + + if (wzDirectory && *wzDirectory) + { + hr = StrAllocString(&sczTempPath, wzDirectory, 0); + ExitOnFailure(hr, "Failed to copy temp path."); + + hr = PathBackslashTerminate(&sczTempPath); + ExitOnFailure(hr, "Failed to ensure path ends in backslash: %ls", wzDirectory); + } + else + { + hr = StrAlloc(&sczTempPath, cchTempPath); + ExitOnFailure(hr, "Failed to allocate memory for the temp path."); + + if (!::GetTempPathW(cchTempPath, sczTempPath)) + { + ExitWithLastError(hr, "Failed to get temp path."); + } + } + + for (DWORD i = 1; i <= dwUniqueCount; ++i) + { + hr = StrAllocFormatted(&scz, wzDirectoryNameTemplate, i); + ExitOnFailure(hr, "Failed to allocate memory for directory name template."); + + hr = StrAllocFormatted(psczTempDirectory, L"%s%s", sczTempPath, scz); + ExitOnFailure(hr, "Failed to allocate temp directory name."); + + if (!::CreateDirectoryW(*psczTempDirectory, NULL)) + { + DWORD er = ::GetLastError(); + if (ERROR_ALREADY_EXISTS == er) + { + hr = HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); + continue; + } + else if (ERROR_PATH_NOT_FOUND == er) + { + hr = DirEnsureExists(*psczTempDirectory, NULL); + break; + } + else + { + hr = HRESULT_FROM_WIN32(er); + break; + } + } + else + { + hr = S_OK; + break; + } + } + ExitOnFailure(hr, "Failed to create temp directory."); + + hr = PathBackslashTerminate(psczTempDirectory); + ExitOnFailure(hr, "Failed to ensure temp directory is backslash terminated."); + +LExit: + ReleaseStr(scz); + ReleaseStr(sczTempPath); + + return hr; +} + + +DAPI_(HRESULT) PathGetKnownFolder( + __in int csidl, + __out LPWSTR* psczKnownFolder + ) +{ + HRESULT hr = S_OK; + + hr = StrAlloc(psczKnownFolder, MAX_PATH); + ExitOnFailure(hr, "Failed to allocate memory for known folder."); + + hr = ::SHGetFolderPathW(NULL, csidl, NULL, SHGFP_TYPE_CURRENT, *psczKnownFolder); + ExitOnFailure(hr, "Failed to get known folder path."); + + hr = PathBackslashTerminate(psczKnownFolder); + ExitOnFailure(hr, "Failed to ensure known folder path is backslash terminated."); + +LExit: + return hr; +} + + +DAPI_(BOOL) PathIsAbsolute( + __in_z LPCWSTR wzPath + ) +{ + DWORD dwLength = lstrlenW(wzPath); + return (1 < dwLength) && (wzPath[0] == L'\\') || (wzPath[1] == L':'); +} + + +DAPI_(HRESULT) PathConcat( + __in_opt LPCWSTR wzPath1, + __in_opt LPCWSTR wzPath2, + __deref_out_z LPWSTR* psczCombined + ) +{ + HRESULT hr = S_OK; + + if (!wzPath2 || !*wzPath2) + { + hr = StrAllocString(psczCombined, wzPath1, 0); + ExitOnFailure(hr, "Failed to copy just path1 to output."); + } + else if (!wzPath1 || !*wzPath1 || PathIsAbsolute(wzPath2)) + { + hr = StrAllocString(psczCombined, wzPath2, 0); + ExitOnFailure(hr, "Failed to copy just path2 to output."); + } + else + { + hr = StrAllocString(psczCombined, wzPath1, 0); + ExitOnFailure(hr, "Failed to copy path1 to output."); + + hr = PathBackslashTerminate(psczCombined); + ExitOnFailure(hr, "Failed to backslashify."); + + hr = StrAllocConcat(psczCombined, wzPath2, 0); + ExitOnFailure(hr, "Failed to append path2 to output."); + } + +LExit: + return hr; +} + + +DAPI_(HRESULT) PathEnsureQuoted( + __inout LPWSTR* ppszPath, + __in BOOL fDirectory + ) +{ + Assert(ppszPath && *ppszPath); + + HRESULT hr = S_OK; + size_t cchPath = 0; + + hr = ::StringCchLengthW(*ppszPath, STRSAFE_MAX_CCH, &cchPath); + ExitOnFailure(hr, "Failed to get the length of the path."); + + // Handle simple special cases. + if (0 == cchPath || (1 == cchPath && L'"' == (*ppszPath)[0])) + { + hr = StrAllocString(ppszPath, L"\"\"", 2); + ExitOnFailure(hr, "Failed to allocate a quoted empty string."); + + ExitFunction(); + } + + if (L'"' != (*ppszPath)[0]) + { + hr = StrAllocPrefix(ppszPath, L"\"", 1); + ExitOnFailure(hr, "Failed to allocate an opening quote."); + + // Add a char for the opening quote. + ++cchPath; + } + + if (L'"' != (*ppszPath)[cchPath - 1]) + { + hr = StrAllocConcat(ppszPath, L"\"", 1); + ExitOnFailure(hr, "Failed to allocate a closing quote."); + + // Add a char for the closing quote. + ++cchPath; + } + + if (fDirectory) + { + if (L'\\' != (*ppszPath)[cchPath - 2]) + { + // Change the last char to a backslash and re-append the closing quote. + (*ppszPath)[cchPath - 1] = L'\\'; + + hr = StrAllocConcat(ppszPath, L"\"", 1); + ExitOnFailure(hr, "Failed to allocate another closing quote after the backslash."); + } + } + +LExit: + + return hr; +} + + +DAPI_(HRESULT) PathCompare( + __in_z LPCWSTR wzPath1, + __in_z LPCWSTR wzPath2, + __out int* pnResult + ) +{ + HRESULT hr = S_OK; + LPWSTR sczPath1 = NULL; + LPWSTR sczPath2 = NULL; + + hr = PathExpand(&sczPath1, wzPath1, PATH_EXPAND_ENVIRONMENT | PATH_EXPAND_FULLPATH); + ExitOnFailure(hr, "Failed to expand path1."); + + hr = PathExpand(&sczPath2, wzPath2, PATH_EXPAND_ENVIRONMENT | PATH_EXPAND_FULLPATH); + ExitOnFailure(hr, "Failed to expand path2."); + + *pnResult = ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, sczPath1, -1, sczPath2, -1); + +LExit: + ReleaseStr(sczPath2); + ReleaseStr(sczPath1); + + return hr; +} + + +DAPI_(HRESULT) PathCompress( + __in_z LPCWSTR wzPath + ) +{ + HRESULT hr = S_OK; + HANDLE hPath = INVALID_HANDLE_VALUE; + + hPath = ::CreateFileW(wzPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (INVALID_HANDLE_VALUE == hPath) + { + ExitWithLastError(hr, "Failed to open path %ls for compression.", wzPath); + } + + DWORD dwBytesReturned = 0; + USHORT usCompressionFormat = COMPRESSION_FORMAT_DEFAULT; + if (0 == ::DeviceIoControl(hPath, FSCTL_SET_COMPRESSION, &usCompressionFormat, sizeof(usCompressionFormat), NULL, 0, &dwBytesReturned, NULL)) + { + // ignore compression attempts on file systems that don't support it + DWORD er = ::GetLastError(); + if (ERROR_INVALID_FUNCTION != er) + { + ExitOnWin32Error(er, hr, "Failed to set compression state for path %ls.", wzPath); + } + } + +LExit: + ReleaseFile(hPath); + + return hr; +} + +DAPI_(HRESULT) PathGetHierarchyArray( + __in_z LPCWSTR wzPath, + __deref_inout_ecount_opt(*pcStrArray) LPWSTR **prgsczPathArray, + __inout LPUINT pcPathArray + ) +{ + HRESULT hr = S_OK; + LPWSTR sczPathCopy = NULL; + LPWSTR sczNewPathCopy = NULL; + DWORD cArraySpacesNeeded = 0; + + for (int i = 0; i < lstrlenW(wzPath); ++i) + { + if (wzPath[i] == L'\\') + { + ++cArraySpacesNeeded; + } + } + if (wzPath[lstrlenW(wzPath) - 1] != L'\\') + { + ++cArraySpacesNeeded; + } + + // 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. + if (wzPath[0] == L'\\' && wzPath[1] == L'\\') + { + cArraySpacesNeeded -= 3; + } + + Assert(cArraySpacesNeeded >= 1); + + hr = MemEnsureArraySize(reinterpret_cast(prgsczPathArray), cArraySpacesNeeded, sizeof(LPWSTR), 0); + ExitOnFailure(hr, "Failed to allocate array of size %u for parent directories", cArraySpacesNeeded); + *pcPathArray = cArraySpacesNeeded; + + hr = StrAllocString(&sczPathCopy, wzPath, 0); + ExitOnFailure(hr, "Failed to allocate copy of original path"); + + for (DWORD i = 0; i < cArraySpacesNeeded; ++i) + { + hr = StrAllocString((*prgsczPathArray) + cArraySpacesNeeded - 1 - i, sczPathCopy, 0); + ExitOnFailure(hr, "Failed to copy path"); + + // 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 + if (wzPath[lstrlenW(sczPathCopy) - 1] == L'\\') + { + sczPathCopy[lstrlenW(sczPathCopy) - 1] = L'\0'; + } + + hr = PathGetDirectory(sczPathCopy, &sczNewPathCopy); + ExitOnFailure(hr, "Failed to get directory portion of path"); + + ReleaseStr(sczPathCopy); + sczPathCopy = sczNewPathCopy; + sczNewPathCopy = NULL; + } + + hr = S_OK; + +LExit: + ReleaseStr(sczPathCopy); + + return hr; +} -- cgit v1.2.3-55-g6feb