From eb53852d7ae6838e54525eb57df1d8ce8a722f9b Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 24 Jun 2022 12:28:27 -0500 Subject: Add longPathAware to Burn manifest to support long paths. Fixes 3455 --- src/burn/engine/approvedexe.cpp | 9 + src/burn/engine/cache.cpp | 6 +- src/burn/engine/engine.cpp | 2 +- src/burn/engine/exeengine.cpp | 7 + src/burn/engine/logging.cpp | 18 +- src/burn/engine/msuengine.cpp | 9 +- src/burn/engine/userexperience.cpp | 5 +- src/burn/engine/variable.cpp | 75 ++-- src/burn/test/BurnUnitTest/RegistrationTest.cpp | 6 +- .../ExternalExecutable.cs | 288 ++++++++++++++- .../ExternalExecutableResult.cs | 4 +- .../TestDataFolderFileSystem.cs | 2 +- src/libs/dutil/WixToolset.DUtil/apputil.cpp | 28 +- src/libs/dutil/WixToolset.DUtil/cabcutil.cpp | 86 ++--- src/libs/dutil/WixToolset.DUtil/cabutil.cpp | 68 ++-- src/libs/dutil/WixToolset.DUtil/dirutil.cpp | 64 +--- src/libs/dutil/WixToolset.DUtil/fileutil.cpp | 12 +- src/libs/dutil/WixToolset.DUtil/inc/dirutil.h | 3 +- src/libs/dutil/WixToolset.DUtil/inc/pathutil.h | 67 +++- src/libs/dutil/WixToolset.DUtil/inc/shelutil.h | 15 + src/libs/dutil/WixToolset.DUtil/logutil.cpp | 19 +- src/libs/dutil/WixToolset.DUtil/path2utl.cpp | 145 +++++++- src/libs/dutil/WixToolset.DUtil/pathutil.cpp | 404 +++++++++++++++++---- src/libs/dutil/WixToolset.DUtil/rexutil.cpp | 77 ++-- src/libs/dutil/WixToolset.DUtil/sceutil.cpp | 11 +- src/libs/dutil/WixToolset.DUtil/shelutil.cpp | 210 ++++++++++- src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp | 182 +++++++++- src/test/burn/Directory.wixproj.targets | 1 + .../NonCompressedBundle.wixproj | 19 + .../NonCompressedBundle/NonCompressedBundle.wxs | 10 + .../LongPathTests/PackageA/PackageA.wixproj | 10 + src/test/burn/TestData/Templates/Bundle.wxs | 5 +- src/test/burn/TestExe/Task.cs | 67 ++++ src/test/burn/WixTestTools/BundleInstaller.cs | 4 +- src/test/burn/WixTestTools/MSIExec.cs | 14 +- src/test/burn/WixTestTools/TestTool.cs | 2 +- .../burn/WixToolsetTest.BurnE2E/LongPathTests.cs | 298 +++++++++++++++ .../testhost.longpathaware.manifest | 11 + src/test/burn/test_burn.cmd | 5 + .../Bundles/CreateBundleExeCommand.cs | 4 + .../BundleFixture.cs | 10 +- 41 files changed, 1890 insertions(+), 392 deletions(-) create mode 100644 src/test/burn/TestData/LongPathTests/NonCompressedBundle/NonCompressedBundle.wixproj create mode 100644 src/test/burn/TestData/LongPathTests/NonCompressedBundle/NonCompressedBundle.wxs create mode 100644 src/test/burn/TestData/LongPathTests/PackageA/PackageA.wixproj create mode 100644 src/test/burn/WixToolsetTest.BurnE2E/LongPathTests.cs create mode 100644 src/test/burn/WixToolsetTest.BurnE2E/testhost.longpathaware.manifest diff --git a/src/burn/engine/approvedexe.cpp b/src/burn/engine/approvedexe.cpp index 2a96868e..d8bd956b 100644 --- a/src/burn/engine/approvedexe.cpp +++ b/src/burn/engine/approvedexe.cpp @@ -148,6 +148,7 @@ extern "C" HRESULT ApprovedExesLaunch( LPWSTR sczCommand = NULL; LPWSTR sczCommandObfuscated = NULL; LPWSTR sczExecutableDirectory = NULL; + size_t cchExecutableDirectory = 0; STARTUPINFOW si = { }; PROCESS_INFORMATION pi = { }; @@ -177,9 +178,17 @@ extern "C" HRESULT ApprovedExesLaunch( // Try to get the directory of the executable so we can set the current directory of the process to help those executables // that expect stuff to be relative to them. Best effort only. hr = PathGetDirectory(pLaunchApprovedExe->sczExecutablePath, &sczExecutableDirectory); + if (SUCCEEDED(hr)) + { + // CreateProcessW has undocumented MAX_PATH restriction for lpCurrentDirectory even when long path support is enabled. + hr = ::StringCchLengthW(sczExecutableDirectory, MAX_PATH - 1, &cchExecutableDirectory); + } + if (FAILED(hr)) { ReleaseNullStr(sczExecutableDirectory); + + hr = S_OK; } LogId(REPORT_STANDARD, MSG_LAUNCHING_APPROVED_EXE, pLaunchApprovedExe->sczExecutablePath, sczCommandObfuscated); diff --git a/src/burn/engine/cache.cpp b/src/burn/engine/cache.cpp index cf9de1c3..eb5cc508 100644 --- a/src/burn/engine/cache.cpp +++ b/src/burn/engine/cache.cpp @@ -174,7 +174,7 @@ extern "C" HRESULT CacheInitialize( // Cache paths are initialized once so they cannot be changed while the engine is caching payloads. // Always construct the default machine package cache path so we can determine if we're redirected. - hr = PathGetKnownFolder(CSIDL_COMMON_APPDATA, &sczAppData); + hr = ShelGetFolder(&sczAppData, CSIDL_COMMON_APPDATA); ExitOnFailure(hr, "Failed to find local %hs appdata directory.", "per-machine"); hr = PathConcat(sczAppData, PACKAGE_CACHE_FOLDER_NAME, &pCache->sczDefaultMachinePackageCache); @@ -210,7 +210,7 @@ extern "C" HRESULT CacheInitialize( pCache->fCustomMachinePackageCache = !fPathEqual; - hr = PathGetKnownFolder(CSIDL_LOCAL_APPDATA, &sczAppData); + hr = ShelGetFolder(&sczAppData, CSIDL_LOCAL_APPDATA); ExitOnFailure(hr, "Failed to find local %hs appdata directory.", "per-user"); hr = PathConcat(sczAppData, PACKAGE_CACHE_FOLDER_NAME, &pCache->sczDefaultUserPackageCache); @@ -1469,7 +1469,7 @@ static HRESULT CalculateWorkingFolders( HRESULT hr = S_OK; LPWSTR sczBaseAcquisitionPath = NULL; - hr = PathGetTempPath(&sczBaseAcquisitionPath); + hr = PathGetTempPath(&sczBaseAcquisitionPath, NULL); ExitOnFailure(hr, "Failed to get temp folder path for acquisition folder base."); hr = PathBackslashTerminate(&sczBaseAcquisitionPath); diff --git a/src/burn/engine/engine.cpp b/src/burn/engine/engine.cpp index 7fca1141..13075497 100644 --- a/src/burn/engine/engine.cpp +++ b/src/burn/engine/engine.cpp @@ -486,7 +486,7 @@ static HRESULT RunUntrusted( si.cb = sizeof(si); si.wShowWindow = static_cast(pEngineState->command.nCmdShow); - if (!::CreateProcessW(wzCleanRoomBundlePath, sczFullCommandLine, NULL, NULL, TRUE, 0, 0, NULL, &si, &pi)) + if (!::CreateProcessW(wzCleanRoomBundlePath, sczFullCommandLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { ExitWithLastError(hr, "Failed to launch clean room process: %ls", sczFullCommandLine); } diff --git a/src/burn/engine/exeengine.cpp b/src/burn/engine/exeengine.cpp index f7be082d..701adb74 100644 --- a/src/burn/engine/exeengine.cpp +++ b/src/burn/engine/exeengine.cpp @@ -695,6 +695,7 @@ extern "C" HRESULT ExeEngineRunProcess( BOOL fDelayedCancel = FALSE; BOOL fFireAndForget = BURN_PACKAGE_TYPE_EXE == pPackage->type && pPackage->Exe.fFireAndForget; BOOL fInheritHandles = BURN_PACKAGE_TYPE_BUNDLE == pPackage->type; + size_t cchCachedDirectory = 0; // Always add user supplied arguments last. if (wzUserArgs) @@ -703,6 +704,12 @@ extern "C" HRESULT ExeEngineRunProcess( ExitOnFailure(hr, "Failed to append user args."); } + // CreateProcessW has undocumented MAX_PATH restriction for lpCurrentDirectory even when long path support is enabled. + if (wzCachedDirectory && FAILED(::StringCchLengthW(wzCachedDirectory, MAX_PATH - 1, &cchCachedDirectory))) + { + wzCachedDirectory = NULL; + } + // Make the cache location of the executable the current directory to help those executables // that expect stuff to be relative to them. si.cb = sizeof(si); diff --git a/src/burn/engine/logging.cpp b/src/burn/engine/logging.cpp index 1020d01f..68f0c35b 100644 --- a/src/burn/engine/logging.cpp +++ b/src/burn/engine/logging.cpp @@ -211,7 +211,7 @@ extern "C" void LoggingOpenFailed() LPCWSTR* lpStrings = const_cast(&LOG_FAILED_EVENT_LOG_MESSAGE); WORD wNumStrings = 1; - hr = LogOpen(NULL, L"Setup", L"_Failed", L"txt", FALSE, FALSE, NULL); + hr = LogOpen(NULL, L"Setup", L"_Failed", L"log", FALSE, FALSE, NULL); if (SUCCEEDED(hr)) { ExitFunction(); @@ -965,19 +965,14 @@ static HRESULT GetNonSessionSpecificTempFolder( ) { HRESULT hr = S_OK; - WCHAR wzTempFolder[MAX_PATH] = { }; + LPWSTR sczTempFolder = NULL; SIZE_T cchTempFolder = 0; DWORD dwSessionId = 0; LPWSTR sczSessionId = 0; SIZE_T cchSessionId = 0; - if (!::GetTempPathW(countof(wzTempFolder), wzTempFolder)) - { - ExitWithLastError(hr, "Failed to get temp folder."); - } - - hr = ::StringCchLengthW(wzTempFolder, countof(wzTempFolder), reinterpret_cast(&cchTempFolder)); - ExitOnFailure(hr, "Failed to get length of temp folder."); + hr = PathGetTempPath(&sczTempFolder, &cchTempFolder); + ExitOnFailure(hr, "Failed to get temp folder."); // If our session id is in the TEMP path then remove that part so we get the non-session // specific temporary folder. @@ -989,17 +984,18 @@ static HRESULT GetNonSessionSpecificTempFolder( hr = ::StringCchLengthW(sczSessionId, STRSAFE_MAX_CCH, reinterpret_cast(&cchSessionId)); ExitOnFailure(hr, "Failed to get length of session id string."); - if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzTempFolder + cchTempFolder - cchSessionId, static_cast(cchSessionId), sczSessionId, static_cast(cchSessionId))) + if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, sczTempFolder + cchTempFolder - cchSessionId, static_cast(cchSessionId), sczSessionId, static_cast(cchSessionId))) { cchTempFolder -= cchSessionId; } } - hr = StrAllocString(psczNonSessionTempFolder, wzTempFolder, cchTempFolder); + hr = StrAllocString(psczNonSessionTempFolder, sczTempFolder, cchTempFolder); ExitOnFailure(hr, "Failed to copy temp folder."); LExit: ReleaseStr(sczSessionId); + ReleaseStr(sczTempFolder); return hr; } diff --git a/src/burn/engine/msuengine.cpp b/src/burn/engine/msuengine.cpp index 1b051165..400fdc92 100644 --- a/src/burn/engine/msuengine.cpp +++ b/src/burn/engine/msuengine.cpp @@ -269,7 +269,6 @@ extern "C" HRESULT MsuEngineExecutePackage( HRESULT hr = S_OK; LPWSTR sczCachedDirectory = NULL; LPWSTR sczMsuPath = NULL; - LPWSTR sczWindowsPath = NULL; LPWSTR sczSystemPath = NULL; LPWSTR sczWusaPath = NULL; LPWSTR sczCommand = NULL; @@ -294,15 +293,12 @@ extern "C" HRESULT MsuEngineExecutePackage( // get wusa.exe path if (fUseSysNativePath) { - hr = PathGetKnownFolder(CSIDL_WINDOWS, &sczWindowsPath); - ExitOnFailure(hr, "Failed to find Windows directory."); - - hr = PathConcat(sczWindowsPath, L"SysNative\\", &sczSystemPath); + hr = PathSystemWindowsSubdirectory(L"SysNative\\", &sczSystemPath); ExitOnFailure(hr, "Failed to append SysNative directory."); } else { - hr = PathGetKnownFolder(CSIDL_SYSTEM, &sczSystemPath); + hr = PathGetSystemDirectory(&sczSystemPath); ExitOnFailure(hr, "Failed to find System32 directory."); } @@ -390,7 +386,6 @@ LExit: ReleaseStr(sczCachedDirectory); ReleaseStr(sczMsuPath); ReleaseStr(sczSystemPath); - ReleaseStr(sczWindowsPath); ReleaseStr(sczWusaPath); ReleaseStr(sczCommand); ReleaseStr(sczEscapedKB); diff --git a/src/burn/engine/userexperience.cpp b/src/burn/engine/userexperience.cpp index a97234ef..6f84caba 100644 --- a/src/burn/engine/userexperience.cpp +++ b/src/burn/engine/userexperience.cpp @@ -99,6 +99,7 @@ extern "C" HRESULT UserExperienceLoad( HRESULT hr = S_OK; BOOTSTRAPPER_CREATE_ARGS args = { }; BOOTSTRAPPER_CREATE_RESULTS results = { }; + LPCWSTR wzPath = pUserExperience->payloads.rgPayloads[0].sczLocalFilePath; args.cbSize = sizeof(BOOTSTRAPPER_CREATE_ARGS); args.pCommand = pCommand; @@ -109,8 +110,8 @@ extern "C" HRESULT UserExperienceLoad( results.cbSize = sizeof(BOOTSTRAPPER_CREATE_RESULTS); // Load BA DLL. - pUserExperience->hUXModule = ::LoadLibraryExW(pUserExperience->payloads.rgPayloads[0].sczLocalFilePath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); - ExitOnNullWithLastError(pUserExperience->hUXModule, hr, "Failed to load BA DLL."); + pUserExperience->hUXModule = ::LoadLibraryExW(wzPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + ExitOnNullWithLastError(pUserExperience->hUXModule, hr, "Failed to load BA DLL: %ls", wzPath); // Get BootstrapperApplicationCreate entry-point. PFN_BOOTSTRAPPER_APPLICATION_CREATE pfnCreate = (PFN_BOOTSTRAPPER_APPLICATION_CREATE)::GetProcAddress(pUserExperience->hUXModule, "BootstrapperApplicationCreate"); diff --git a/src/burn/engine/variable.cpp b/src/burn/engine/variable.cpp index c96e9d95..8d208a66 100644 --- a/src/burn/engine/variable.cpp +++ b/src/burn/engine/variable.cpp @@ -1947,18 +1947,18 @@ static HRESULT InitializeVariableTempFolder( UNREFERENCED_PARAMETER(dwpData); HRESULT hr = S_OK; - WCHAR wzPath[MAX_PATH] = { }; + LPWSTR sczPath = NULL; - if (!::GetTempPathW(MAX_PATH, wzPath)) - { - ExitWithLastError(hr, "Failed to get temp path."); - } + hr = PathGetTempPath(&sczPath, NULL); + ExitOnFailure(hr, "Failed to get temp path."); // set value - hr = BVariantSetString(pValue, wzPath, 0, FALSE); + hr = BVariantSetString(pValue, sczPath, 0, FALSE); ExitOnFailure(hr, "Failed to set variant value."); LExit: + ReleaseStr(sczPath); + return hr; } @@ -1969,7 +1969,7 @@ static HRESULT InitializeVariableSystemFolder( { HRESULT hr = S_OK; BOOL f64 = (BOOL)dwpData; - WCHAR wzSystemFolder[MAX_PATH + 2] = { }; + LPWSTR sczSystemFolder = NULL; #if !defined(_WIN64) BOOL fIsWow64 = FALSE; @@ -1979,57 +1979,43 @@ static HRESULT InitializeVariableSystemFolder( { if (f64) { - if (!::GetSystemDirectoryW(wzSystemFolder, countof(wzSystemFolder))) - { - ExitWithLastError(hr, "Failed to get 64-bit system folder."); - } + hr = PathGetSystemDirectory(&sczSystemFolder); + ExitOnFailure(hr, "Failed to get 64-bit system folder."); } else { - if (!::GetSystemWow64DirectoryW(wzSystemFolder, countof(wzSystemFolder))) - { - ExitWithLastError(hr, "Failed to get 32-bit system folder."); - } + hr = PathGetSystemWow64Directory(&sczSystemFolder); + ExitOnFailure(hr, "Failed to get 32-bit system folder."); } } else { if (!f64) { - if (!::GetSystemDirectoryW(wzSystemFolder, countof(wzSystemFolder))) - { - ExitWithLastError(hr, "Failed to get 32-bit system folder."); - } + hr = PathGetSystemDirectory(&sczSystemFolder); + ExitOnFailure(hr, "Failed to get 32-bit system folder."); } } #else if (f64) { - if (!::GetSystemDirectoryW(wzSystemFolder, countof(wzSystemFolder))) - { - ExitWithLastError(hr, "Failed to get 64-bit system folder."); - } + hr = PathGetSystemDirectory(&sczSystemFolder); + ExitOnFailure(hr, "Failed to get 64-bit system folder."); } else { - if (!::GetSystemWow64DirectoryW(wzSystemFolder, countof(wzSystemFolder))) - { - ExitWithLastError(hr, "Failed to get 32-bit system folder."); - } + hr = PathGetSystemWow64Directory(&sczSystemFolder); + ExitOnFailure(hr, "Failed to get 32-bit system folder."); } #endif - if (*wzSystemFolder) - { - hr = PathFixedBackslashTerminate(wzSystemFolder, countof(wzSystemFolder)); - ExitOnFailure(hr, "Failed to backslash terminate system folder."); - } - // set value - hr = BVariantSetString(pValue, wzSystemFolder, 0, FALSE); + hr = BVariantSetString(pValue, sczSystemFolder, 0, FALSE); ExitOnFailure(hr, "Failed to set system folder variant value."); LExit: + ReleaseStr(sczSystemFolder); + return hr; } @@ -2041,26 +2027,25 @@ static HRESULT InitializeVariableWindowsVolumeFolder( UNREFERENCED_PARAMETER(dwpData); HRESULT hr = S_OK; - WCHAR wzWindowsPath[MAX_PATH] = { }; - WCHAR wzVolumePath[MAX_PATH] = { }; + LPWSTR sczWindowsPath = NULL; + LPWSTR sczVolumePath = NULL; // get windows directory - if (!::GetWindowsDirectoryW(wzWindowsPath, countof(wzWindowsPath))) - { - ExitWithLastError(hr, "Failed to get windows directory."); - } + hr = PathSystemWindowsSubdirectory(NULL, &sczWindowsPath); + ExitOnFailure(hr, "Failed to get windows directory."); // get volume path name - if (!::GetVolumePathNameW(wzWindowsPath, wzVolumePath, MAX_PATH)) - { - ExitWithLastError(hr, "Failed to get volume path name."); - } + hr = PathGetVolumePathName(sczWindowsPath, &sczVolumePath); + ExitOnFailure(hr, "Failed to get volume path name."); // set value - hr = BVariantSetString(pValue, wzVolumePath, 0, FALSE); + hr = BVariantSetString(pValue, sczVolumePath, 0, FALSE); ExitOnFailure(hr, "Failed to set variant value."); LExit: + ReleaseStr(sczWindowsPath); + ReleaseStr(sczVolumePath); + return hr; } diff --git a/src/burn/test/BurnUnitTest/RegistrationTest.cpp b/src/burn/test/BurnUnitTest/RegistrationTest.cpp index 883b9cc8..aa3bd34b 100644 --- a/src/burn/test/BurnUnitTest/RegistrationTest.cpp +++ b/src/burn/test/BurnUnitTest/RegistrationTest.cpp @@ -108,8 +108,8 @@ namespace Bootstrapper TestThrowOnFailure(hr, L"Failed to register bundle."); // verify that registration was created - Assert::True(Directory::Exists(cacheDirectory)); - Assert::True(File::Exists(Path::Combine(cacheDirectory, gcnew String(L"setup.exe")))); + Assert::True(Directory::Exists(cacheDirectory), "Cache directory didn't exist."); + Assert::True(File::Exists(Path::Combine(cacheDirectory, gcnew String(L"setup.exe"))), "Bundle exe wasn't cached."); this->ValidateUninstallKeyResume(Int32(BURN_RESUME_MODE_ACTIVE)); this->ValidateRunOnceKeyEntry(cacheExePath); @@ -119,7 +119,7 @@ namespace Bootstrapper TestThrowOnFailure(hr, L"Failed to unregister bundle."); // verify that registration was removed - Assert::False(Directory::Exists(cacheDirectory)); + Assert::False(Directory::Exists(cacheDirectory), "Cache directory wasn't removed."); this->ValidateUninstallKeyNull(L"Resume"); this->ValidateRunOnceKeyString(TEST_BUNDLE_ID, nullptr); diff --git a/src/internal/WixBuildTools.TestSupport/ExternalExecutable.cs b/src/internal/WixBuildTools.TestSupport/ExternalExecutable.cs index eb07aa13..4a932645 100644 --- a/src/internal/WixBuildTools.TestSupport/ExternalExecutable.cs +++ b/src/internal/WixBuildTools.TestSupport/ExternalExecutable.cs @@ -2,11 +2,16 @@ namespace WixBuildTools.TestSupport { + using System; using System.Collections.Concurrent; using System.Collections.Generic; + using System.ComponentModel; using System.Diagnostics; using System.IO; + using System.Runtime.InteropServices; using System.Text; + using System.Threading.Tasks; + using Microsoft.Win32.SafeHandles; public abstract class ExternalExecutable { @@ -18,6 +23,125 @@ namespace WixBuildTools.TestSupport } protected ExternalExecutableResult Run(string args, bool mergeErrorIntoOutput = false, string workingDirectory = null) + { + // https://github.com/dotnet/runtime/issues/58492 + // Process.Start doesn't currently support starting a process with a long path, + // but the way to support long paths doesn't support searching for the executable if it was a relative path. + // Avoid the managed way of doing this even if the target isn't a long path to help verify that the native way works. + if (!Path.IsPathRooted(this.exePath)) + { + return this.RunManaged(args, mergeErrorIntoOutput, workingDirectory); + } + + // https://web.archive.org/web/20150331190801/https://support.microsoft.com/en-us/kb/190351 + var commandLine = $"\"{this.exePath}\" {args}"; + var currentDirectory = workingDirectory ?? Path.GetDirectoryName(this.exePath); + if (String.IsNullOrEmpty(currentDirectory)) + { + currentDirectory = null; + } + var processInfo = new PROCESS_INFORMATION(); + var startInfo = new STARTUPINFOW + { + cb = Marshal.SizeOf(typeof(STARTUPINFOW)), + dwFlags = StartupInfoFlags.STARTF_FORCEOFFFEEDBACK | StartupInfoFlags.STARTF_USESTDHANDLES, + hStdInput = GetStdHandle(StdHandleType.STD_INPUT_HANDLE), + }; + SafeFileHandle hStdOutputParent = null; + SafeFileHandle hStdErrorParent = null; + + try + { + CreatePipeForProcess(out hStdOutputParent, out startInfo.hStdOutput); + + if (!mergeErrorIntoOutput) + { + CreatePipeForProcess(out hStdErrorParent, out startInfo.hStdError); + } + else + { + if (!DuplicateHandle(GetCurrentProcess(), startInfo.hStdOutput, GetCurrentProcess(), out startInfo.hStdError, 0, true, DuplicateHandleOptions.DUPLICATE_SAME_ACCESS)) + { + throw new Win32Exception(); + } + } + + if (!CreateProcessW(this.exePath, commandLine, IntPtr.Zero, IntPtr.Zero, true, CreateProcessFlags.CREATE_NO_WINDOW, IntPtr.Zero, + currentDirectory, ref startInfo, ref processInfo)) + { + throw new Win32Exception(); + } + + startInfo.Dispose(); + + return GetResultFromNative(mergeErrorIntoOutput, hStdOutputParent, hStdErrorParent, processInfo.hProcess, this.exePath, args); + } + finally + { + hStdErrorParent?.Dispose(); + hStdOutputParent?.Dispose(); + + startInfo.Dispose(); + processInfo.Dispose(); + } + } + + private static ExternalExecutableResult GetResultFromNative(bool mergeErrorIntoOutput, SafeFileHandle hStdOutputParent, SafeFileHandle hStdErrorParent, IntPtr hProcess, string fileName, string args) + { + using (var outputStream = new StreamReader(new FileStream(hStdOutputParent, FileAccess.Read))) + using (var errorStream = mergeErrorIntoOutput ? null : new StreamReader(new FileStream(hStdErrorParent, FileAccess.Read))) + { + var outputTask = Task.Run(() => ReadProcessStreamLines(outputStream)); + var errorTask = Task.Run(() => ReadProcessStreamLines(errorStream)); + + while (!outputTask.Wait(100) || !errorTask.Wait(100)) { Task.Yield(); } + var standardOutput = outputTask.Result; + var standardError = errorTask.Result; + + if (WaitForSingleObject(hProcess, -1) != 0) + { + throw new Win32Exception(); + } + + if (!GetExitCodeProcess(hProcess, out var exitCode)) + { + throw new Win32Exception(); + } + + return new ExternalExecutableResult + { + ExitCode = exitCode, + StandardError = standardError, + StandardOutput = standardOutput, + FileName = fileName, + Arguments = args, + }; + } + } + + private static string[] ReadProcessStreamLines(StreamReader streamReader) + { + if (streamReader == null) + { + return null; + } + + var lines = new List(); + while (true) + { + var line = streamReader.ReadLine(); + if (line == null) + { + break; + } + + lines.Add(line); + } + + return lines.ToArray(); + } + + protected ExternalExecutableResult RunManaged(string args, bool mergeErrorIntoOutput = false, string workingDirectory = null) { var startInfo = new ProcessStartInfo(this.exePath, args) { @@ -48,7 +172,8 @@ namespace WixBuildTools.TestSupport ExitCode = process.ExitCode, StandardError = mergeErrorIntoOutput ? null : standardError.ToArray(), StandardOutput = standardOutput.ToArray(), - StartInfo = startInfo, + FileName = this.exePath, + Arguments = args, }; } } @@ -84,5 +209,166 @@ namespace WixBuildTools.TestSupport return sb.ToString(); } + + private static void CreatePipeForProcess(out SafeFileHandle hReadPipe, out IntPtr hWritePipe) + { + var securityAttributes = new SECURITY_ATTRIBUTES + { + nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES)), + bInheritHandle = true, + }; + + if (!CreatePipe(out var hReadTemp, out hWritePipe, ref securityAttributes, 0)) + { + throw new Win32Exception(); + } + + // Only the handle passed to the process should be inheritable, so have to duplicate the other handle to get an uninheritable one. + if (!DuplicateHandle(GetCurrentProcess(), hReadTemp, GetCurrentProcess(), out var hReadPipePtr, 0, false, DuplicateHandleOptions.DUPLICATE_CLOSE_SOURCE | DuplicateHandleOptions.DUPLICATE_SAME_ACCESS)) + { + throw new Win32Exception(); + } + + hReadPipe = new SafeFileHandle(hReadPipePtr, true); + } + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + private extern static IntPtr GetStdHandle(StdHandleType nStdHandle); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private extern static bool CreatePipe(out IntPtr hReadPipe, out IntPtr hWritePipe, ref SECURITY_ATTRIBUTES lpPipeAttributes, int nSize); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private extern static bool CreateProcessW( + string lpApplicationName, + string lpCommandLine, + IntPtr lpProcessAttributes, + IntPtr lpThreadAttributes, + [MarshalAs(UnmanagedType.Bool)] bool bInheritHandles, + CreateProcessFlags dwCreationFlags, + IntPtr lpEnvironment, + string lpCurrentDirectory, + ref STARTUPINFOW lpStartupInfo, + ref PROCESS_INFORMATION lpProcessInformation); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)] + private extern static IntPtr GetCurrentProcess(); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private extern static bool GetExitCodeProcess(IntPtr hHandle, out int lpExitCode); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + private extern static int WaitForSingleObject(IntPtr hHandle, int dwMilliseconds); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private extern static bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private extern static bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, int dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, DuplicateHandleOptions dwOptions); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct SECURITY_ATTRIBUTES + { + public int nLength; + public IntPtr lpSecurityDescriptor; + [MarshalAs(UnmanagedType.Bool)] + public bool bInheritHandle; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct STARTUPINFOW + { + public int cb; + public string lpReserved; + public string lpDesktop; + public string lpTitle; + public int dwX; + public int dwY; + public int dwXSize; + public int dwYSize; + public int dwXCountChars; + public int dwYCountChars; + public int dwFillAttribute; + public StartupInfoFlags dwFlags; + public short wShowWindow; + public short cbReserved2; + public IntPtr lpReserved2; + public IntPtr hStdInput; + public IntPtr hStdOutput; + public IntPtr hStdError; + + public void Dispose() + { + // This makes assumptions based on how it's used above. + if (this.hStdError != IntPtr.Zero) + { + CloseHandle(this.hStdError); + this.hStdError = IntPtr.Zero; + } + + if (this.hStdOutput != IntPtr.Zero) + { + CloseHandle(this.hStdOutput); + this.hStdOutput = IntPtr.Zero; + } + } + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct PROCESS_INFORMATION + { + public IntPtr hProcess; + public IntPtr hThread; + public int dwProcessId; + public int dwThreadId; + + public void Dispose() + { + if (this.hProcess != IntPtr.Zero) + { + CloseHandle(this.hProcess); + this.hProcess = IntPtr.Zero; + } + + if (this.hThread != IntPtr.Zero) + { + CloseHandle(this.hThread); + this.hThread = IntPtr.Zero; + } + } + } + + private enum StdHandleType + { + STD_INPUT_HANDLE = -10, + STD_OUTPUT_HANDLE = -11, + STD_ERROR_HANDLE = -12, + } + + [Flags] + private enum CreateProcessFlags + { + None = 0x0, + CREATE_NO_WINDOW = 0x08000000, + } + + [Flags] + private enum StartupInfoFlags + { + None = 0x0, + STARTF_FORCEOFFFEEDBACK = 0x80, + STARTF_USESTDHANDLES = 0x100, + } + + private enum DuplicateHandleOptions + { + DUPLICATE_CLOSE_SOURCE = 1, + DUPLICATE_SAME_ACCESS = 2, + } } } diff --git a/src/internal/WixBuildTools.TestSupport/ExternalExecutableResult.cs b/src/internal/WixBuildTools.TestSupport/ExternalExecutableResult.cs index 19b5183b..950ee4bd 100644 --- a/src/internal/WixBuildTools.TestSupport/ExternalExecutableResult.cs +++ b/src/internal/WixBuildTools.TestSupport/ExternalExecutableResult.cs @@ -12,6 +12,8 @@ namespace WixBuildTools.TestSupport public string[] StandardOutput { get; set; } - public ProcessStartInfo StartInfo { get; set; } + public string FileName { get; set; } + + public string Arguments { get; set; } } } diff --git a/src/internal/WixBuildTools.TestSupport/TestDataFolderFileSystem.cs b/src/internal/WixBuildTools.TestSupport/TestDataFolderFileSystem.cs index 8d670bf0..15b7631d 100644 --- a/src/internal/WixBuildTools.TestSupport/TestDataFolderFileSystem.cs +++ b/src/internal/WixBuildTools.TestSupport/TestDataFolderFileSystem.cs @@ -33,7 +33,7 @@ namespace WixBuildTools.TestSupport RobocopyFolder(sourceDirectoryPath, this.BaseFolder); } - private static ExternalExecutableResult RobocopyFolder(string sourceFolderPath, string destinationFolderPath) + public static ExternalExecutableResult RobocopyFolder(string sourceFolderPath, string destinationFolderPath) { var args = $"\"{sourceFolderPath}\" \"{destinationFolderPath}\" /E /R:1 /W:1"; return RobocopyRunner.Execute(args); diff --git a/src/libs/dutil/WixToolset.DUtil/apputil.cpp b/src/libs/dutil/WixToolset.DUtil/apputil.cpp index b70c8cfb..9e75082a 100644 --- a/src/libs/dutil/WixToolset.DUtil/apputil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/apputil.cpp @@ -87,31 +87,27 @@ DAPI_(HRESULT) LoadSystemLibraryWithPath( ) { HRESULT hr = S_OK; - DWORD cch = 0; - WCHAR wzPath[MAX_PATH] = { }; + LPWSTR sczDirectory = NULL; + LPWSTR sczPath = NULL; - cch = ::GetSystemDirectoryW(wzPath, MAX_PATH); - AppExitOnNullWithLastError(cch, hr, "Failed to get the Windows system directory."); + hr = PathGetSystemDirectory(&sczDirectory); + AppExitOnFailure(hr, "Failed to get the Windows system directory."); - if (L'\\' != wzPath[cch - 1]) - { - hr = ::StringCchCatNW(wzPath, MAX_PATH, L"\\", 1); - AppExitOnRootFailure(hr, "Failed to terminate the string with a backslash."); - } + hr = StrAllocFormatted(&sczPath, L"%ls%ls", sczDirectory, wzModuleName); + AppExitOnFailure(hr, "Failed to create the fully-qualified path to %ls.", wzModuleName); - hr = ::StringCchCatW(wzPath, MAX_PATH, wzModuleName); - AppExitOnRootFailure(hr, "Failed to create the fully-qualified path to %ls.", wzModuleName); - - *phModule = ::LoadLibraryExW(wzPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); - AppExitOnNullWithLastError(*phModule, hr, "Failed to load the library %ls.", wzModuleName); + *phModule = ::LoadLibraryExW(sczPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + AppExitOnNullWithLastError(*phModule, hr, "Failed to load the library %ls.", sczPath); if (psczPath) { - hr = StrAllocString(psczPath, wzPath, MAX_PATH); - AppExitOnFailure(hr, "Failed to copy the path to library."); + *psczPath = sczPath; + sczPath = NULL; } LExit: + ReleaseStr(sczDirectory); + return hr; } diff --git a/src/libs/dutil/WixToolset.DUtil/cabcutil.cpp b/src/libs/dutil/WixToolset.DUtil/cabcutil.cpp index d1edc54d..294669af 100644 --- a/src/libs/dutil/WixToolset.DUtil/cabcutil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/cabcutil.cpp @@ -89,8 +89,8 @@ struct CABC_DATA STRINGDICT_HANDLE shDictHandle; - WCHAR wzCabinetPath[MAX_PATH]; - WCHAR wzEmptyFile[MAX_PATH]; + LPWSTR sczCabinetPath; + LPWSTR sczEmptyFile; HANDLE hEmptyFile; DWORD dwLastFileIndex; @@ -197,33 +197,17 @@ extern "C" HRESULT DAPI CabCBegin( HRESULT hr = S_OK; CABC_DATA *pcd = NULL; - WCHAR wzTempPath[MAX_PATH] = { }; C_ASSERT(sizeof(MSIFILEHASHINFO) == 20); - WCHAR wzPathBuffer [MAX_PATH] = L""; - size_t cchPathBuffer; + LPWSTR pwzPathBuffer = NULL; if (wzCabDir) { - hr = ::StringCchLengthW(wzCabDir, MAX_PATH, &cchPathBuffer); - CabcExitOnFailure(hr, "Failed to get length of cab directory"); - - // Need room to terminate with L'\\' and L'\0' - if((MAX_PATH - 1) <= cchPathBuffer || 0 == cchPathBuffer) - { - hr = E_INVALIDARG; - CabcExitOnFailure(hr, "Cab directory had invalid length: %u", cchPathBuffer); - } - - hr = ::StringCchCopyW(wzPathBuffer, countof(wzPathBuffer), wzCabDir); + hr = StrAllocString(&pwzPathBuffer, wzCabDir, 0); CabcExitOnFailure(hr, "Failed to copy cab directory to buffer"); - if (L'\\' != wzPathBuffer[cchPathBuffer - 1]) - { - hr = ::StringCchCatW(wzPathBuffer, countof(wzPathBuffer), L"\\"); - CabcExitOnFailure(hr, "Failed to cat \\ to end of buffer"); - ++cchPathBuffer; - } + hr = PathBackslashTerminate(&pwzPathBuffer); + CabcExitOnFailure(hr, "Failed to cat \\ to end of buffer"); } pcd = static_cast(MemAlloc(sizeof(CABC_DATA), TRUE)); @@ -290,33 +274,23 @@ extern "C" HRESULT DAPI CabCBegin( CabcExitWithLastError(hr, "failed to convert cab name to multi-byte"); } - if (0 == ::WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, wzPathBuffer, -1, pcd->ccab.szCabPath, sizeof(pcd->ccab.szCab), NULL, NULL)) + if (0 == ::WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, pwzPathBuffer, -1, pcd->ccab.szCabPath, sizeof(pcd->ccab.szCab), NULL, NULL)) { CabcExitWithLastError(hr, "failed to convert cab dir to multi-byte"); } // Remember the path to the cabinet. - hr= ::StringCchCopyW(pcd->wzCabinetPath, countof(pcd->wzCabinetPath), wzPathBuffer); - CabcExitOnFailure(hr, "Failed to copy cabinet path from path: %ls", wzPathBuffer); - - hr = ::StringCchCatW(pcd->wzCabinetPath, countof(pcd->wzCabinetPath), wzCab); + hr = PathConcat(pwzPathBuffer, wzCab, &pcd->sczCabinetPath); CabcExitOnFailure(hr, "Failed to concat to cabinet path cabinet name: %ls", wzCab); // Get the empty file to use as the blank marker for duplicates. - if (!::GetTempPathW(countof(wzTempPath), wzTempPath)) - { - CabcExitWithLastError(hr, "Failed to get temp path."); - } - - if (!::GetTempFileNameW(wzTempPath, L"WSC", 0, pcd->wzEmptyFile)) - { - CabcExitWithLastError(hr, "Failed to create a temp file name."); - } + hr = DirCreateTempPath(L"WSC", &pcd->sczEmptyFile); + CabcExitOnFailure(hr, "Failed to create a temp file name."); // Try to open the newly created empty file (remember, GetTempFileName() is kind enough to create a file for us) // with a handle to automatically delete the file on close. Ignore any failure that might happen, since the worst // case is we'll leave a zero byte file behind in the temp folder. - pcd->hEmptyFile = ::CreateFileW(pcd->wzEmptyFile, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL); + pcd->hEmptyFile = ::CreateFileW(pcd->sczEmptyFile, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL); hr = DictCreateWithEmbeddedKey(&pcd->shDictHandle, dwMaxFiles, reinterpret_cast(&pcd->prgFiles), offsetof(CABC_FILE, pwzSourcePath), DICT_FLAG_CASEINSENSITIVE); CabcExitOnFailure(hr, "Failed to create dictionary to keep track of duplicate files"); @@ -358,6 +332,8 @@ extern "C" HRESULT DAPI CabCBegin( *phContext = pcd; LExit: + ReleaseStr(pwzPathBuffer); + if (FAILED(hr) && pcd && pcd->hfci) { ::FCIDestroy(pcd->hfci); @@ -527,7 +503,7 @@ extern "C" HRESULT DAPI CabCFinish( // files point at the same path (the empty file) so there is no point in tracking them with // their path. fileInfo.wzSourcePath = pcd->prgDuplicates[dwDupeArrayFileIndex].pwzSourcePath; - fileInfo.wzEmptyPath = pcd->wzEmptyFile; + fileInfo.wzEmptyPath = pcd->sczEmptyFile; // Use the provided token, otherwise default to the source file name. if (pcd->prgDuplicates[dwDupeArrayFileIndex].pwzToken) @@ -643,7 +619,7 @@ extern "C" HRESULT DAPI CabCFinish( if (pcd->fGoodCab && pcd->cDuplicates) { hr = UpdateDuplicateFiles(pcd); - CabcExitOnFailure(hr, "Failed to update duplicates in cabinet: %ls", pcd->wzCabinetPath); + CabcExitOnFailure(hr, "Failed to update duplicates in cabinet: %ls", pcd->sczCabinetPath); } LExit: @@ -692,6 +668,9 @@ static void FreeCabCData( ReleaseMem(pcd->prgFiles); ReleaseMem(pcd->prgDuplicates); + ReleaseStr(pcd->sczCabinetPath); + ReleaseStr(pcd->sczEmptyFile); + ReleaseMem(pcd); } } @@ -709,7 +688,7 @@ static HRESULT CheckForDuplicateFile( __in LONGLONG llFileSize ) { - DWORD i; + DWORD i = 0; HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; @@ -916,17 +895,17 @@ static HRESULT UpdateDuplicateFiles( LPVOID pv = NULL; MS_CABINET_HEADER *pCabinetHeader = NULL; - hCabinet = ::CreateFileW(pcd->wzCabinetPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + hCabinet = ::CreateFileW(pcd->sczCabinetPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hCabinet) { - CabcExitWithLastError(hr, "Failed to open cabinet: %ls", pcd->wzCabinetPath); + CabcExitWithLastError(hr, "Failed to open cabinet: %ls", pcd->sczCabinetPath); } // Shouldn't need more than 16 MB to get the whole cabinet header into memory so use that as // the upper bound for the memory map. if (!::GetFileSizeEx(hCabinet, &liCabinetSize)) { - CabcExitWithLastError(hr, "Failed to get size of cabinet: %ls", pcd->wzCabinetPath); + CabcExitWithLastError(hr, "Failed to get size of cabinet: %ls", pcd->sczCabinetPath); } if (0 == liCabinetSize.HighPart && liCabinetSize.LowPart < MAX_CABINET_HEADER_SIZE) @@ -942,11 +921,11 @@ static HRESULT UpdateDuplicateFiles( hCabinetMapping = ::CreateFileMappingW(hCabinet, NULL, PAGE_READWRITE | SEC_COMMIT, 0, cbCabinet, NULL); if (NULL == hCabinetMapping || INVALID_HANDLE_VALUE == hCabinetMapping) { - CabcExitWithLastError(hr, "Failed to memory map cabinet file: %ls", pcd->wzCabinetPath); + CabcExitWithLastError(hr, "Failed to memory map cabinet file: %ls", pcd->sczCabinetPath); } pv = ::MapViewOfFile(hCabinetMapping, FILE_MAP_WRITE, 0, 0, 0); - CabcExitOnNullWithLastError(pv, hr, "Failed to map view of cabinet file: %ls", pcd->wzCabinetPath); + CabcExitOnNullWithLastError(pv, hr, "Failed to map view of cabinet file: %ls", pcd->sczCabinetPath); pCabinetHeader = static_cast(pv); @@ -1155,7 +1134,7 @@ static __callback INT_PTR DIAMONDAPI CabCOpen( if (INVALID_HANDLE_VALUE == reinterpret_cast(pFile)) { - CabcExitOnLastError(hr, "failed to open file: %s", pszFile); + CabcExitOnLastError(hr, "failed to open file: %hs", pszFile); } LExit: @@ -1326,11 +1305,12 @@ static __callback BOOL DIAMONDAPI CabCGetTempFile( HRESULT hr = S_OK; char szTempPath[MAX_PATH] = { }; - DWORD cchTempPath = MAX_PATH; DWORD dwProcessId = ::GetCurrentProcessId(); HANDLE hTempFile = INVALID_HANDLE_VALUE; - if (MAX_PATH < ::GetTempPathA(cchTempPath, szTempPath)) + // TODO: Allow user to pass in different temp path in case the default is too long, + // and/or see if magic similar to CABC_MAGIC_UNICODE_STRING_MARKER can be used to pass ourselves a path longer than MAX_PATH. + if (MAX_PATH < ::GetTempPathA(countof(szTempPath), szTempPath)) { CabcExitWithLastError(hr, "Failed to get temp path during cabinet creation."); } @@ -1339,7 +1319,7 @@ static __callback BOOL DIAMONDAPI CabCGetTempFile( { LONG dwTempIndex = ::InterlockedIncrement(reinterpret_cast(&dwIndex)); - hr = ::StringCbPrintfA(szFile, cbFile, "%s\\%08x.%03x", szTempPath, dwTempIndex, dwProcessId); + hr = ::StringCbPrintfA(szFile, cbFile, "%hs\\%08x.%03x", szTempPath, dwTempIndex, dwProcessId); CabcExitOnFailure(hr, "failed to format log file path."); hTempFile = ::CreateFileA(szFile, 0, FILE_SHARE_DELETE, NULL, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL); @@ -1351,7 +1331,11 @@ static __callback BOOL DIAMONDAPI CabCGetTempFile( } else { - hr = E_FAIL; // this file was taken so be pessimistic and assume we're not going to find one. + hr = HRESULT_FROM_WIN32(::GetLastError()); // this file was taken so be pessimistic and assume we're not going to find one. + if (SUCCEEDED(hr)) + { + hr = E_FAIL; + } } } CabcExitOnFailure(hr, "failed to find temporary file."); @@ -1386,7 +1370,7 @@ static __callback BOOL DIAMONDAPI CabCGetNextCabinet( if (pccab->iCab == 1) { pcd->wzFirstCabinetName[0] = '\0'; - LPCWSTR pwzCabinetName = PathFile(pcd->wzCabinetPath); + LPCWSTR pwzCabinetName = PathFile(pcd->sczCabinetPath); size_t len = wcsnlen(pwzCabinetName, sizeof(pwzCabinetName)); if (len > 4) { diff --git a/src/libs/dutil/WixToolset.DUtil/cabutil.cpp b/src/libs/dutil/WixToolset.DUtil/cabutil.cpp index f3629d57..57463e1a 100644 --- a/src/libs/dutil/WixToolset.DUtil/cabutil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/cabutil.cpp @@ -261,15 +261,15 @@ static HRESULT DAPI CabOperation( ) { HRESULT hr = S_OK; - BOOL fResult; + BOOL fResult = FALSE; LPWSTR sczCabinet = NULL; LPWSTR pwz = NULL; - CHAR szCabDirectory[MAX_PATH * 4]; // Make sure these are big enough for UTF-8 strings - CHAR szCabFile[MAX_PATH * 4]; + LPSTR pszCabDirectory = NULL; + CHAR szCabFile[MAX_PATH * 4] = { }; // Make sure this is big enough for UTF-8 strings - CAB_CALLBACK_STRUCT ccs; - PFNFDINOTIFY pfnFdiNotify; + CAB_CALLBACK_STRUCT ccs = { }; + PFNFDINOTIFY pfnFdiNotify = NULL; // // ensure the cabinet.dll is loaded @@ -299,15 +299,13 @@ static HRESULT DAPI CabOperation( // If a full path was not provided, use the relative current directory. if (wzCabinet == pwz) { - hr = ::StringCchCopyA(szCabDirectory, countof(szCabDirectory), ".\\"); + hr = StrAnsiAllocStringAnsi(&pszCabDirectory, ".\\", 0); CabExitOnFailure(hr, "Failed to copy relative current directory as cabinet directory."); } else { - if (!::WideCharToMultiByte(CP_UTF8, 0, sczCabinet, -1, szCabDirectory, countof(szCabDirectory), NULL, NULL)) - { - CabExitWithLastError(hr, "failed to convert cabinet directory to ASCII: %ls", sczCabinet); - } + hr = StrAnsiAllocString(&pszCabDirectory, sczCabinet, 0, CP_UTF8); + CabExitOnFailure(hr, "failed to convert cabinet directory to ASCII: %ls", sczCabinet); } // @@ -331,7 +329,7 @@ static HRESULT DAPI CabOperation( v_pfnNetFx11Notify = pfnNotify; pfnFdiNotify = FDINotify; } - fResult = vpfnFDICopy(vhfdi, szCabFile, szCabDirectory, 0, pfnFdiNotify, NULL, static_cast(&ccs)); + fResult = vpfnFDICopy(vhfdi, szCabFile, pszCabDirectory, 0, pfnFdiNotify, NULL, static_cast(&ccs)); if (!fResult && !ccs.fStopExtracting) // if something went wrong and it wasn't us just stopping the extraction, then return a failure { CabExitWithLastError(hr, "failed to extract cabinet file: %ls", sczCabinet); @@ -339,6 +337,7 @@ static HRESULT DAPI CabOperation( LExit: ReleaseStr(sczCabinet); + ReleaseStr(pszCabDirectory); v_pfnNetFx11Notify = NULL; return hr; @@ -493,14 +492,15 @@ static __callback INT_PTR DIAMONDAPI CabExtractCallback(__in FDINOTIFICATIONTYPE INT_PTR ipResult = 0; // result to return on success CAB_CALLBACK_STRUCT* pccs = static_cast(pFDINotify->pv); - LPCSTR sz; - WCHAR wz[MAX_PATH]; - FILETIME ft; + LPCSTR sz = NULL; + LPWSTR pwz = NULL; + LPWSTR pwzPath = NULL; + FILETIME ft = { }; switch (iNotification) { case fdintCOPY_FILE: // begin extracting a resource from cabinet - CabExitOnNull(pFDINotify->psz1, hr, E_INVALIDARG, "No cabinet file ID given to convert"); + Assert(pccs && pFDINotify->psz1); CabExitOnNull(pccs, hr, E_INVALIDARG, "Failed to call cabextract callback, because no callback struct was provided"); if (pccs->fStopExtracting) @@ -510,40 +510,37 @@ static __callback INT_PTR DIAMONDAPI CabExtractCallback(__in FDINOTIFICATIONTYPE // convert params to useful variables sz = static_cast(pFDINotify->psz1); - if (!::MultiByteToWideChar(CP_ACP, 0, sz, -1, wz, countof(wz))) - { - CabExitWithLastError(hr, "failed to convert cabinet file id to unicode: %s", sz); - } + CabExitOnNull(sz, hr, E_INVALIDARG, "No cabinet file ID given to convert"); + + hr = StrAllocStringAnsi(&pwz, sz, 0, CP_ACP); + CabExitOnFailure(hr, "failed to convert cabinet file id to unicode: %hs", sz); if (pccs->pfnProgress) { - hr = pccs->pfnProgress(TRUE, wz, pccs->pvContext); + hr = pccs->pfnProgress(TRUE, pwz, pccs->pvContext); if (S_OK != hr) { ExitFunction(); } } - if (L'*' == *pccs->pwzExtract || 0 == lstrcmpW(pccs->pwzExtract, wz)) + if (L'*' == *pccs->pwzExtract || 0 == lstrcmpW(pccs->pwzExtract, pwz)) { // get the created date for the resource in the cabinet FILETIME ftLocal; if (!::DosDateTimeToFileTime(pFDINotify->date, pFDINotify->time, &ftLocal)) { - CabExitWithLastError(hr, "failed to get time for resource: %ls", wz); + CabExitWithLastError(hr, "failed to get time for resource: %ls", pwz); } ::LocalFileTimeToFileTime(&ftLocal, &ft); - WCHAR wzPath[MAX_PATH]; - hr = ::StringCchCopyW(wzPath, countof(wzPath), pccs->pwzExtractDir); - CabExitOnFailure(hr, "failed to copy in extract directory: %ls for file: %ls", pccs->pwzExtractDir, wz); - hr = ::StringCchCatW(wzPath, countof(wzPath), wz); - CabExitOnFailure(hr, "failed to concat onto path: %ls file: %ls", wzPath, wz); + hr = PathConcat(pccs->pwzExtractDir, pwz, &pwzPath); + CabExitOnFailure(hr, "failed to concat onto path: %ls file: %ls", pccs->pwzExtractDir, pwz); - hFile = OpenFileWithRetry(wzPath, GENERIC_WRITE, CREATE_ALWAYS); + hFile = OpenFileWithRetry(pwzPath, GENERIC_WRITE, CREATE_ALWAYS); if (INVALID_HANDLE_VALUE == hFile) { - CabExitWithLastError(hr, "failed to create file: %ls", wzPath); + CabExitWithLastError(hr, "failed to create file: %ls", pwzPath); } ::SetFileTime(hFile, &ft, &ft, &ft); // try to set the file time (who cares if it fails) @@ -567,17 +564,15 @@ static __callback INT_PTR DIAMONDAPI CabExtractCallback(__in FDINOTIFICATIONTYPE break; case fdintCLOSE_FILE_INFO: // resource extraction complete - Assert(pFDINotify->hf && pFDINotify->psz1); + Assert(pFDINotify->hf && pccs && pFDINotify->psz1); CabExitOnNull(pccs, hr, E_INVALIDARG, "Failed to call cabextract callback, because no callback struct was provided"); // convert params to useful variables sz = static_cast(pFDINotify->psz1); CabExitOnNull(sz, hr, E_INVALIDARG, "Failed to convert cabinet file id, because no cabinet file id was provided"); - if (!::MultiByteToWideChar(CP_ACP, 0, sz, -1, wz, countof(wz))) - { - CabExitWithLastError(hr, "failed to convert cabinet file id to unicode: %s", sz); - } + hr = StrAllocStringAnsi(&pwz, sz, 0, CP_ACP); + CabExitOnFailure(hr, "failed to convert cabinet file id to unicode: %hs", sz); if (NULL != pFDINotify->hf) // just close the file { @@ -586,7 +581,7 @@ static __callback INT_PTR DIAMONDAPI CabExtractCallback(__in FDINOTIFICATIONTYPE if (pccs->pfnProgress) { - hr = pccs->pfnProgress(FALSE, wz, pccs->pvContext); + hr = pccs->pfnProgress(FALSE, pwz, pccs->pvContext); } if (S_OK == hr && L'*' == *pccs->pwzExtract) // if everything is okay and we're extracting all files, keep going @@ -613,5 +608,8 @@ static __callback INT_PTR DIAMONDAPI CabExtractCallback(__in FDINOTIFICATIONTYPE LExit: ReleaseFileHandle(hFile); + ReleaseStr(pwz); + ReleaseStr(pwzPath); + return (S_OK == hr) ? ipResult : -1; } diff --git a/src/libs/dutil/WixToolset.DUtil/dirutil.cpp b/src/libs/dutil/WixToolset.DUtil/dirutil.cpp index 94eab9e7..2c02225d 100644 --- a/src/libs/dutil/WixToolset.DUtil/dirutil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/dirutil.cpp @@ -59,34 +59,10 @@ LExit: *******************************************************************/ extern "C" HRESULT DAPI DirCreateTempPath( __in_z LPCWSTR wzPrefix, - __out_ecount_z(cchPath) LPWSTR wzPath, - __in DWORD cchPath + __out_opt LPWSTR* psczTempFile ) { - Assert(wzPrefix); - Assert(wzPath); - - HRESULT hr = S_OK; - - WCHAR wzDir[MAX_PATH]; - WCHAR wzFile[MAX_PATH]; - DWORD cch = 0; - - cch = ::GetTempPathW(countof(wzDir), wzDir); - if (!cch || cch >= countof(wzDir)) - { - DirExitWithLastError(hr, "Failed to GetTempPath."); - } - - if (!::GetTempFileNameW(wzDir, wzPrefix, 0, wzFile)) - { - DirExitWithLastError(hr, "Failed to GetTempFileName."); - } - - hr = ::StringCchCopyW(wzPath, cchPath, wzFile); - -LExit: - return hr; + return PathCreateTempFile(NULL, NULL, 0, wzPrefix, 0, psczTempFile, NULL); } @@ -192,18 +168,19 @@ extern "C" HRESULT DAPI DirEnsureDeleteEx( Assert(wzPath && *wzPath); HRESULT hr = S_OK; - DWORD er; + DWORD er = ERROR_SUCCESS; - DWORD dwAttrib; + DWORD dwAttrib = 0; HANDLE hFind = INVALID_HANDLE_VALUE; LPWSTR sczDelete = NULL; - WIN32_FIND_DATAW wfd; + WIN32_FIND_DATAW wfd = { }; BOOL fDeleteFiles = (DIR_DELETE_FILES == (dwFlags & DIR_DELETE_FILES)); BOOL fRecurse = (DIR_DELETE_RECURSE == (dwFlags & DIR_DELETE_RECURSE)); BOOL fScheduleDelete = (DIR_DELETE_SCHEDULE == (dwFlags & DIR_DELETE_SCHEDULE)); - WCHAR wzTempDirectory[MAX_PATH] = { }; - WCHAR wzTempPath[MAX_PATH] = { }; + WCHAR wzSafeFileName[MAX_PATH + 1] = { }; + LPWSTR sczTempDirectory = NULL; + LPWSTR sczTempPath = NULL; if (-1 == (dwAttrib = ::GetFileAttributesW(wzPath))) { @@ -231,10 +208,8 @@ extern "C" HRESULT DAPI DirEnsureDeleteEx( { if (fScheduleDelete) { - if (!::GetTempPathW(countof(wzTempDirectory), wzTempDirectory)) - { - DirExitWithLastError(hr, "Failed to get temp directory."); - } + hr = PathGetTempPath(&sczTempDirectory, NULL); + DirExitOnFailure(hr, "Failed to get temp directory."); } // Delete everything in this directory. @@ -256,10 +231,11 @@ extern "C" HRESULT DAPI DirEnsureDeleteEx( } // For extra safety and to silence OACR. - wfd.cFileName[MAX_PATH - 1] = L'\0'; + hr = ::StringCchCopyNExW(wzSafeFileName, countof(wzSafeFileName), wfd.cFileName, countof(wfd.cFileName), NULL, NULL, STRSAFE_FILL_BEHIND_NULL | STRSAFE_NULL_ON_FAILURE); + DirExitOnFailure(hr, "Failed to ensure file name was null terminated."); - hr = PathConcat(wzPath, wfd.cFileName, &sczDelete); - DirExitOnFailure(hr, "Failed to concat filename '%ls' to directory: %ls", wfd.cFileName, wzPath); + hr = PathConcat(wzPath, wzSafeFileName, &sczDelete); + DirExitOnFailure(hr, "Failed to concat filename '%ls' to directory: %ls", wzSafeFileName, wzPath); if (fRecurse && wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { @@ -288,16 +264,14 @@ extern "C" HRESULT DAPI DirEnsureDeleteEx( { if (fScheduleDelete) { - if (!::GetTempFileNameW(wzTempDirectory, L"DEL", 0, wzTempPath)) - { - DirExitWithLastError(hr, "Failed to get temp file to move to."); - } + hr = PathGetTempFileName(sczTempDirectory, L"DEL", 0, &sczTempPath); + DirExitOnFailure(hr, "Failed to get temp file to move to."); // Try to move the file to the temp directory then schedule for delete, // otherwise just schedule for delete. - if (::MoveFileExW(sczDelete, wzTempPath, MOVEFILE_REPLACE_EXISTING)) + if (::MoveFileExW(sczDelete, sczTempPath, MOVEFILE_REPLACE_EXISTING)) { - ::MoveFileExW(wzTempPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT); + ::MoveFileExW(sczTempPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT); } else { @@ -348,6 +322,8 @@ extern "C" HRESULT DAPI DirEnsureDeleteEx( LExit: ReleaseFileFindHandle(hFind); ReleaseStr(sczDelete); + ReleaseStr(sczTempDirectory); + ReleaseStr(sczTempPath); return hr; } diff --git a/src/libs/dutil/WixToolset.DUtil/fileutil.cpp b/src/libs/dutil/WixToolset.DUtil/fileutil.cpp index 9f68ee52..ac407916 100644 --- a/src/libs/dutil/WixToolset.DUtil/fileutil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/fileutil.cpp @@ -1284,21 +1284,18 @@ extern "C" HRESULT DAPI FileCreateTempW( Assert(wzPrefix && *wzPrefix); HRESULT hr = E_FAIL; - WCHAR wzTempPath[MAX_PATH]; - DWORD cchTempPath = countof(wzTempPath); + LPWSTR pwzTempPath = NULL; LPWSTR pwzTempFile = NULL; HANDLE hTempFile = INVALID_HANDLE_VALUE; int i = 0; - if (!::GetTempPathW(cchTempPath, wzTempPath)) - { - FileExitOnLastError(hr, "failed to get temp path"); - } + hr = PathGetTempPath(&pwzTempPath, NULL); + FileExitOnFailure(hr, "failed to get temp path"); for (i = 0; i < 1000 && INVALID_HANDLE_VALUE == hTempFile; ++i) { - hr = StrAllocFormatted(&pwzTempFile, L"%s%s%05d.%s", wzTempPath, wzPrefix, i, wzExtension); + hr = StrAllocFormatted(&pwzTempFile, L"%s%s%05d.%s", pwzTempPath, wzPrefix, i, wzExtension); FileExitOnFailure(hr, "failed to allocate memory for temp filename"); hTempFile = ::CreateFileW(pwzTempFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); @@ -1330,6 +1327,7 @@ extern "C" HRESULT DAPI FileCreateTempW( LExit: ReleaseFile(hTempFile); ReleaseStr(pwzTempFile); + ReleaseStr(pwzTempPath); return hr; } diff --git a/src/libs/dutil/WixToolset.DUtil/inc/dirutil.h b/src/libs/dutil/WixToolset.DUtil/inc/dirutil.h index 42268a16..b8fc0431 100644 --- a/src/libs/dutil/WixToolset.DUtil/inc/dirutil.h +++ b/src/libs/dutil/WixToolset.DUtil/inc/dirutil.h @@ -20,8 +20,7 @@ BOOL DAPI DirExists( HRESULT DAPI DirCreateTempPath( __in_z LPCWSTR wzPrefix, - __out_ecount_z(cchPath) LPWSTR wzPath, - __in DWORD cchPath + __out_opt LPWSTR* psczTempFile ); HRESULT DAPI DirEnsureExists( diff --git a/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h b/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h index 971ef887..de46b95d 100644 --- a/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h +++ b/src/libs/dutil/WixToolset.DUtil/inc/pathutil.h @@ -159,18 +159,30 @@ DAPI_(HRESULT) PathRelativeToModule( /******************************************************************* PathCreateTempFile - Note: if wzDirectory is null, ::GetTempPath() will be used instead. + Note: if wzDirectory is null, ::GetTempPath2() will be used instead. if wzFileNameTemplate is null, GetTempFileName() will be used instead. *******************************************************************/ DAPI_(HRESULT) PathCreateTempFile( __in_opt LPCWSTR wzDirectory, __in_opt __format_string LPCWSTR wzFileNameTemplate, __in DWORD dwUniqueCount, + __in_z LPCWSTR wzPrefix, __in DWORD dwFileAttributes, __out_opt LPWSTR* psczTempFile, __out_opt HANDLE* phTempFile ); +/******************************************************************* + PathGetTempFileName - wrapper around ::GetTempFileName. + If the wzPathName is too long, it will use its own algorithm. +*******************************************************************/ +DAPI_(HRESULT) PathGetTempFileName( + __in LPCWSTR wzPathName, + __in LPCWSTR wzPrefixString, + __in UINT uUnique, + __out LPWSTR* psczTempFileName + ); + /******************************************************************* PathCreateTimeBasedTempFile - creates an empty temp file based on current system time @@ -187,7 +199,7 @@ DAPI_(HRESULT) PathCreateTimeBasedTempFile( /******************************************************************* PathCreateTempDirectory - Note: if wzDirectory is null, ::GetTempPath() will be used instead. + Note: if wzDirectory is null, ::GetTempPath2() will be used instead. *******************************************************************/ DAPI_(HRESULT) PathCreateTempDirectory( __in_opt LPCWSTR wzDirectory, @@ -201,7 +213,24 @@ DAPI_(HRESULT) PathCreateTempDirectory( that is backslash terminated. *******************************************************************/ DAPI_(HRESULT) PathGetTempPath( - __out_z LPWSTR* psczTempPath + __out_z LPWSTR* psczTempPath, + __out_opt SIZE_T* pcch + ); + +/******************************************************************* + PathGetSystemDirectory - returns the path to the system folder + that is backslash terminated. +*******************************************************************/ +DAPI_(HRESULT) PathGetSystemDirectory( + __out_z LPWSTR* psczSystemPath + ); + +/******************************************************************* + PathGetSystemWow64Directory - returns the path to the system WoW 64 folder + that is backslash terminated. +*******************************************************************/ +DAPI_(HRESULT) PathGetSystemWow64Directory( + __out_z LPWSTR* psczSystemPath ); /******************************************************************* @@ -223,12 +252,11 @@ DAPI_(HRESULT) PathGetSystemTempPaths( ); /******************************************************************* - PathGetKnownFolder - returns the path to a well-known shell folder - + PathGetVolumePathName - wrapper for ::GetVolumePathNameW. *******************************************************************/ -DAPI_(HRESULT) PathGetKnownFolder( - __in int csidl, - __out LPWSTR* psczKnownFolder +DAPI_(HRESULT) PathGetVolumePathName( + __in_z LPCWSTR wzFileName, + __out_z LPWSTR* psczVolumePathName ); /******************************************************************* @@ -340,6 +368,20 @@ DAPI_(HRESULT) PathGetHierarchyArray( __inout LPUINT pcPathArray ); +/******************************************************************** + Path2FunctionAllowFallback - allow functions only available in newer versions of Windows. + Typically used for unit testing. + +*********************************************************************/ +void DAPI Path2FunctionAllowFallback(); + +/******************************************************************** + Path2FunctionForceFallback - ignore functions only available in newer versions of Windows. + Typically used for unit testing. + +*********************************************************************/ +void DAPI Path2FunctionForceFallback(); + /******************************************************************* PathCanonicalizePath - wrapper around PathCanonicalizeW. *******************************************************************/ @@ -348,6 +390,15 @@ DAPI_(HRESULT) PathCanonicalizePath( __deref_out_z LPWSTR* psczCanonicalized ); +/******************************************************************* + PathAllocCanonicalizePath - wrapper around PathAllocCanonicalize. +*******************************************************************/ +DAPI_(HRESULT) PathAllocCanonicalizePath( + __in_z LPCWSTR wzPath, + __in DWORD dwFlags, + __deref_out_z LPWSTR* psczCanonicalized + ); + /******************************************************************* PathCanonicalizeForComparison - canonicalizes the path based on the given flags. . and .. directories are collapsed. diff --git a/src/libs/dutil/WixToolset.DUtil/inc/shelutil.h b/src/libs/dutil/WixToolset.DUtil/inc/shelutil.h index 0b9f539d..2ee7ce87 100644 --- a/src/libs/dutil/WixToolset.DUtil/inc/shelutil.h +++ b/src/libs/dutil/WixToolset.DUtil/inc/shelutil.h @@ -33,10 +33,25 @@ HRESULT DAPI ShelExecUnelevated( __in_z_opt LPCWSTR wzWorkingDirectory, __in int nShowCmd ); + +/******************************************************************** + ShelGetFolder() - translates the CSIDL into KNOWNFOLDERID and calls ShelGetKnownFolder. + If that returns E_NOTIMPL then falls back to ::SHGetFolderPathW. + The CSIDL_FLAG values are not supported, CSIDL_FLAG_CREATE is always used. + The path is backslash terminated. + +*******************************************************************/ HRESULT DAPI ShelGetFolder( __out_z LPWSTR* psczFolderPath, __in int csidlFolder ); + +/******************************************************************** + ShelGetKnownFolder() - gets a folder by KNOWNFOLDERID with ::SHGetKnownFolderPath. + The path is backslash terminated. + + Note: return E_NOTIMPL if called on pre-Vista operating systems. +*******************************************************************/ HRESULT DAPI ShelGetKnownFolder( __out_z LPWSTR* psczFolderPath, __in REFKNOWNFOLDERID rfidFolder diff --git a/src/libs/dutil/WixToolset.DUtil/logutil.cpp b/src/libs/dutil/WixToolset.DUtil/logutil.cpp index 88a90d8c..3a130b4e 100644 --- a/src/libs/dutil/WixToolset.DUtil/logutil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/logutil.cpp @@ -684,9 +684,10 @@ LExit: extern "C" HRESULT DAPI LogHeader() { HRESULT hr = S_OK; - WCHAR wzComputerName[MAX_PATH]; + WCHAR wzComputerName[MAX_COMPUTERNAME_LENGTH + 1] = { }; DWORD cchComputerName = countof(wzComputerName); - WCHAR wzPath[MAX_PATH]; + LPWSTR sczPath = NULL; + LPCWSTR wzPath = NULL; DWORD dwMajorVersion = 0; DWORD dwMinorVersion = 0; LPCSTR szLevel = LOGUTIL_UNKNOWN; @@ -695,12 +696,19 @@ extern "C" HRESULT DAPI LogHeader() // // get the interesting data // - if (!::GetModuleFileNameW(NULL, wzPath, countof(wzPath))) + + hr = PathForCurrentProcess(&sczPath, NULL); + if (FAILED(hr)) + { + wzPath = L""; + } + else { - memset(wzPath, 0, sizeof(wzPath)); + wzPath = sczPath; + + hr = FileVersion(wzPath, &dwMajorVersion, &dwMinorVersion); } - hr = FileVersion(wzPath, &dwMajorVersion, &dwMinorVersion); if (FAILED(hr)) { dwMajorVersion = 0; @@ -743,6 +751,7 @@ extern "C" HRESULT DAPI LogHeader() hr = S_OK; ReleaseStr(sczCurrentDateTime); + ReleaseStr(sczPath); return hr; } diff --git a/src/libs/dutil/WixToolset.DUtil/path2utl.cpp b/src/libs/dutil/WixToolset.DUtil/path2utl.cpp index 3c4b2f88..862a743d 100644 --- a/src/libs/dutil/WixToolset.DUtil/path2utl.cpp +++ b/src/libs/dutil/WixToolset.DUtil/path2utl.cpp @@ -19,6 +19,65 @@ #define PathExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_PATHUTIL, g, x, s, __VA_ARGS__) +typedef HRESULT(WINAPI* PFN_PATH_ALLOC_CANONICALIZE)( + __in LPCWSTR wzSource, + __in DWORD dwFlags, + __out_z LPWSTR* psczPathOut + ); + +static BOOL vfInitialized = FALSE; +static HMODULE vhPathApiSet_1_1_0 = NULL; +static PFN_PATH_ALLOC_CANONICALIZE vpfnPathAllocCanonicalize = NULL; +static BOOL vfForceFallback = FALSE; + +// from PathCch.h +#ifndef PATHCCH_ALLOW_LONG_PATHS +#define PATHCCH_ALLOW_LONG_PATHS 0x01 +#endif + +static HRESULT Initialize() +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + + if (vfInitialized) + { + ExitFunction(); + } + + hr = LoadSystemApiSet(L"api-ms-win-core-path-l1-1-0.dll", &vhPathApiSet_1_1_0); + if (E_MODNOTFOUND == hr) + { + hr = E_NOTIMPL; + } + PathExitOnFailure(hr, "Failed to load api-ms-win-core-path-l1-1-0.dll"); + + vpfnPathAllocCanonicalize = reinterpret_cast(::GetProcAddress(vhPathApiSet_1_1_0, "PathAllocCanonicalize")); + if (!vpfnPathAllocCanonicalize) + { + er = ::GetLastError(); + PathExitWithRootFailure(hr, ERROR_PROC_NOT_FOUND == er ? E_NOTIMPL : HRESULT_FROM_WIN32(er), "Failed to get address of PathAllocCanonicalize."); + } + + vfInitialized = TRUE; + +LExit: + return hr; +} + + +DAPI_(void) Path2FunctionAllowFallback() +{ + vfForceFallback = FALSE; +} + + +DAPI_(void) Path2FunctionForceFallback() +{ + vfForceFallback = TRUE; +} + + DAPI_(HRESULT) PathCanonicalizePath( __in_z LPCWSTR wzPath, __deref_out_z LPWSTR* psczCanonicalized @@ -43,6 +102,37 @@ LExit: return hr; } +DAPI_(HRESULT) PathAllocCanonicalizePath( + __in_z LPCWSTR wzPath, + __in DWORD dwFlags, + __deref_out_z LPWSTR* psczCanonicalized + ) +{ + HRESULT hr = S_OK; + LPWSTR sczCanonicalizedPath = NULL; + + hr = Initialize(); + if (E_NOTIMPL == hr || SUCCEEDED(hr) && !vpfnPathAllocCanonicalize) + { + ExitFunction1(hr = E_NOTIMPL); + } + PathExitOnFailure(hr, "Failed to initialize path2utl."); + + hr = vpfnPathAllocCanonicalize(wzPath, dwFlags, &sczCanonicalizedPath); + PathExitOnFailure(hr, "Failed to canonicalize: %ls", wzPath); + + hr = StrAllocString(psczCanonicalized, sczCanonicalizedPath, 0); + PathExitOnFailure(hr, "Failed to copy the canonicalized path."); + +LExit: + if (sczCanonicalizedPath) + { + ::LocalFree(sczCanonicalizedPath); + } + + return hr; +} + DAPI_(HRESULT) PathCanonicalizeForComparison( __in_z LPCWSTR wzPath, __in DWORD dwCanonicalizeFlags, @@ -75,7 +165,19 @@ DAPI_(HRESULT) PathCanonicalizeForComparison( if (*wzNormalizedPath) { - hr = PathCanonicalizePath(wzNormalizedPath, psczCanonicalized); + if (!vfForceFallback) + { + hr = PathAllocCanonicalizePath(wzNormalizedPath, PATHCCH_ALLOW_LONG_PATHS, psczCanonicalized); + } + else + { + hr = E_NOTIMPL; + } + + if (E_NOTIMPL == hr) + { + hr = PathCanonicalizePath(wzNormalizedPath, psczCanonicalized); + } PathExitOnFailure(hr, "Failed to canonicalize: %ls", wzNormalizedPath); } else @@ -273,33 +375,52 @@ DAPI_(HRESULT) PathSystemWindowsSubdirectory( ) { HRESULT hr = S_OK; - WCHAR wzTempPath[MAX_PATH + 1] = { }; + LPWSTR sczWindowsPath = NULL; + DWORD cchBuffer = MAX_PATH + 1; DWORD cch = 0; - cch = ::GetSystemWindowsDirectoryW(wzTempPath, countof(wzTempPath)); - if (!cch) - { - PathExitWithLastError(hr, "Failed to get Windows directory path."); - } - else if (cch >= countof(wzTempPath)) + hr = StrAlloc(&sczWindowsPath, cchBuffer); + PathExitOnFailure(hr, "Failed to alloc Windows directory path."); + + cch = ::GetSystemWindowsDirectoryW(sczWindowsPath, cchBuffer); + PathExitOnNullWithLastError(cch, hr, "Failed to get Windows directory path with default size."); + + cch += 1; // add 1 for null terminator. + + if (cch > cchBuffer) { - PathExitWithRootFailure(hr, E_INSUFFICIENT_BUFFER, "Windows directory path too long."); + hr = StrAlloc(&sczWindowsPath, cch); + PathExitOnFailure(hr, "Failed to realloc Windows directory path."); + + cchBuffer = cch; + + cch = ::GetSystemWindowsDirectoryW(sczWindowsPath, cchBuffer); + PathExitOnNullWithLastError(cch, hr, "Failed to get Windows directory path with returned size."); + + cch += 1; // add 1 for null terminator. + + if (cch > cchBuffer) + { + PathExitWithRootFailure(hr, E_INSUFFICIENT_BUFFER, "Failed to get Windows directory path with returned size."); + } } if (wzSubdirectory) { - hr = PathConcatRelativeToBase(wzTempPath, wzSubdirectory, psczFullPath); + hr = PathConcatRelativeToBase(sczWindowsPath, wzSubdirectory, psczFullPath); PathExitOnFailure(hr, "Failed to concat subdirectory on Windows directory path."); } else { - hr = StrAllocString(psczFullPath, wzTempPath, 0); - PathExitOnFailure(hr, "Failed to copy Windows directory path."); + *psczFullPath = sczWindowsPath; + sczWindowsPath = NULL; } hr = PathBackslashTerminate(psczFullPath); PathExitOnFailure(hr, "Failed to terminate Windows directory path with backslash."); LExit: + ReleaseStr(sczWindowsPath); + return hr; } diff --git a/src/libs/dutil/WixToolset.DUtil/pathutil.cpp b/src/libs/dutil/WixToolset.DUtil/pathutil.cpp index 0e2a5dec..dd5385fc 100644 --- a/src/libs/dutil/WixToolset.DUtil/pathutil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/pathutil.cpp @@ -20,6 +20,11 @@ #define PATH_GOOD_ENOUGH 64 +typedef DWORD(APIENTRY* PFN_GETTEMPPATH2W)( + __in DWORD BufferLength, + __out LPWSTR Buffer + ); + static BOOL IsPathSeparatorChar( __in WCHAR wc ); @@ -527,28 +532,55 @@ DAPI_(HRESULT) PathForCurrentProcess( ) { HRESULT hr = S_OK; - DWORD cch = MAX_PATH; + WCHAR smallBuffer[1] = { }; + SIZE_T cchMax = 0; + DWORD cchBuffer = 0; + DWORD cch = 0; + DWORD dwMaxAttempts = 20; - do + // GetModuleFileNameW didn't originally set the last error when the buffer was too small. + ::SetLastError(ERROR_SUCCESS); + + cch = ::GetModuleFileNameW(hModule, smallBuffer, countof(smallBuffer)); + PathExitOnNullWithLastError(cch, hr, "Failed to get size of path for executing process."); + + if (*psczFullPath && ERROR_INSUFFICIENT_BUFFER == ::GetLastError()) { - hr = StrAlloc(psczFullPath, cch); - PathExitOnFailure(hr, "Failed to allocate string for module path."); + hr = StrMaxLength(*psczFullPath, &cchMax); + PathExitOnFailure(hr, "Failed to get max length of input buffer."); + + cchBuffer = (DWORD)min(DWORD_MAX, cchMax); + } + else + { + cchBuffer = MAX_PATH + 1; + + hr = StrAlloc(psczFullPath, cchBuffer); + PathExitOnFailure(hr, "Failed to allocate space for module path."); + } + + ::SetLastError(ERROR_SUCCESS); - DWORD cchRequired = ::GetModuleFileNameW(hModule, *psczFullPath, cch); - if (0 == cchRequired) + for (DWORD i = 0; i < dwMaxAttempts; ++i) + { + cch = ::GetModuleFileNameW(hModule, *psczFullPath, cchBuffer); + PathExitOnNullWithLastError(cch, hr, "Failed to get path for executing process."); + + if (ERROR_INSUFFICIENT_BUFFER != ::GetLastError()) { - PathExitWithLastError(hr, "Failed to get path for executing process."); + break; } - else if (cchRequired == cch) + + if ((dwMaxAttempts - 1) == i) { - cch = cchRequired + 1; - hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + PathExitWithRootFailure(hr, E_FAIL, "Unexpected failure getting path for executing process."); } - else - { - hr = S_OK; - } - } while (HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) == hr); + + cchBuffer *= 2; + + hr = StrAlloc(psczFullPath, cchBuffer); + PathExitOnFailure(hr, "Failed to re-allocate more space for module path."); + } LExit: return hr; @@ -582,17 +614,18 @@ DAPI_(HRESULT) PathCreateTempFile( __in_opt LPCWSTR wzDirectory, __in_opt __format_string LPCWSTR wzFileNameTemplate, __in DWORD dwUniqueCount, + __in_z LPCWSTR wzPrefix, __in DWORD dwFileAttributes, __out_opt LPWSTR* psczTempFile, __out_opt HANDLE* phTempFile ) { - AssertSz(0 < dwUniqueCount, "Must specify a non-zero unique count."); + Assert(wzPrefix); + AssertSz(!wzFileNameTemplate || !*wzFileNameTemplate || 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; @@ -605,13 +638,8 @@ DAPI_(HRESULT) PathCreateTempFile( } else { - hr = StrAlloc(&sczTempPath, cchTempPath); - PathExitOnFailure(hr, "Failed to allocate memory for the temp path."); - - if (!::GetTempPathW(cchTempPath, sczTempPath)) - { - PathExitWithLastError(hr, "Failed to get temp path."); - } + hr = PathGetTempPath(&sczTempPath, NULL); + PathExitOnFailure(hr, "Failed to get temp path."); } if (wzFileNameTemplate && *wzFileNameTemplate) @@ -621,7 +649,7 @@ DAPI_(HRESULT) PathCreateTempFile( hr = StrAllocFormatted(&scz, wzFileNameTemplate, i); PathExitOnFailure(hr, "Failed to allocate memory for file template."); - hr = StrAllocFormatted(&sczTempFile, L"%s%s", sczTempPath, scz); + hr = StrAllocFormatted(&sczTempFile, L"%ls%ls", sczTempPath, scz); PathExitOnFailure(hr, "Failed to allocate temp file name."); hTempFile = ::CreateFileW(sczTempFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, CREATE_NEW, dwFileAttributes, NULL); @@ -642,13 +670,8 @@ DAPI_(HRESULT) PathCreateTempFile( // the system to provide us a temp file using its built-in mechanism. if (INVALID_HANDLE_VALUE == hTempFile) { - hr = StrAlloc(&sczTempFile, MAX_PATH); - PathExitOnFailure(hr, "Failed to allocate memory for the temp path"); - - if (!::GetTempFileNameW(sczTempPath, L"TMP", 0, sczTempFile)) - { - PathExitWithLastError(hr, "Failed to create new temp file name."); - } + hr = PathGetTempFileName(sczTempPath, wzPrefix, 0, &sczTempFile); + PathExitOnFailure(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) @@ -684,6 +707,82 @@ LExit: } +DAPI_(HRESULT) PathGetTempFileName( + __in LPCWSTR wzPathName, + __in LPCWSTR wzPrefixString, + __in UINT uUnique, + __out LPWSTR* psczTempFileName + ) +{ + HRESULT hr = S_OK; + size_t cchFullPath = 0; + WORD wValue = (WORD)(0xffff & uUnique); + LPWSTR scz = NULL; + LPWSTR sczTempFile = NULL; + HANDLE hTempFile = INVALID_HANDLE_VALUE; + + hr = ::StringCchLengthW(wzPathName, STRSAFE_MAX_CCH, &cchFullPath); + PathExitOnFailure(hr, "Failed to get length of path to prefix."); + + if (MAX_PATH - 14 >= cchFullPath) + { + hr = StrAlloc(psczTempFileName, MAX_PATH); + PathExitOnFailure(hr, "Failed to allocate buffer for GetTempFileNameW."); + + if (!::GetTempFileNameW(wzPathName, wzPrefixString, uUnique, *psczTempFileName)) + { + PathExitWithLastError(hr, "Failed to create new temp file name."); + } + + ExitFunction(); + } + + // TODO: when uUnique is 0, consider not always starting at 0 to avoid collisions if this is called repeatedly. + // Purposely let it wrap around. + for (WORD w = 0; w < WORD_MAX && INVALID_HANDLE_VALUE == hTempFile; ++wValue) + { + hr = StrAllocFormatted(&scz, L"%ls%x.TMP", wzPrefixString, w); + PathExitOnFailure(hr, "Failed to allocate memory for file template."); + + hr = PathConcat(wzPathName, scz, &sczTempFile); + PathExitOnFailure(hr, "Failed to allocate temp file name."); + + hTempFile = ::CreateFileW(sczTempFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, CREATE_NEW, 0, NULL); + if (INVALID_HANDLE_VALUE == hTempFile) + { + // if the file already exists, try next one. + hr = HRESULT_FROM_WIN32(::GetLastError()); + if (HRESULT_FROM_WIN32(ERROR_FILE_EXISTS) == hr) + { + hr = S_OK; + } + PathExitOnFailure(hr, "Failed to create file: %ls", sczTempFile); + } + + ++w; + } + + if (INVALID_HANDLE_VALUE == hTempFile) + { + PathExitWithRootFailure(hr, HRESULT_FROM_WIN32(ERROR_FILE_EXISTS), "Failed to create temp file."); + } + + hr = StrAllocString(psczTempFileName, sczTempFile, 0); + PathExitOnFailure(hr, "Failed to copy temp file string."); + +LExit: + if (INVALID_HANDLE_VALUE != hTempFile) + { + ::CloseHandle(hTempFile); + } + + ReleaseStr(scz); + ReleaseStr(sczTempFile); + + return hr; +} + + DAPI_(HRESULT) PathCreateTimeBasedTempFile( __in_z_opt LPCWSTR wzDirectory, __in_z LPCWSTR wzPrefix, @@ -695,7 +794,7 @@ DAPI_(HRESULT) PathCreateTimeBasedTempFile( { HRESULT hr = S_OK; BOOL fRetry = FALSE; - WCHAR wzTempPath[MAX_PATH] = { }; + LPWSTR sczTempParentPath = NULL; LPWSTR sczPrefix = NULL; LPWSTR sczPrefixFolder = NULL; SYSTEMTIME time = { }; @@ -711,12 +810,10 @@ DAPI_(HRESULT) PathCreateTimeBasedTempFile( } else { - if (!::GetTempPathW(countof(wzTempPath), wzTempPath)) - { - PathExitWithLastError(hr, "Failed to get temp folder."); - } + hr = PathGetTempPath(&sczTempParentPath, NULL); + PathExitOnFailure(hr, "Failed to get temp folder."); - hr = PathConcat(wzTempPath, wzPrefix, &sczPrefix); + hr = PathConcat(sczTempParentPath, wzPrefix, &sczPrefix); PathExitOnFailure(hr, "Failed to concatenate the temp folder and log prefix."); } @@ -778,6 +875,7 @@ DAPI_(HRESULT) PathCreateTimeBasedTempFile( LExit: ReleaseFile(hTempFile); + ReleaseStr(sczTempParentPath); ReleaseStr(sczTempPath); ReleaseStr(sczPrefixFolder); ReleaseStr(sczPrefix); @@ -799,7 +897,6 @@ DAPI_(HRESULT) PathCreateTempDirectory( HRESULT hr = S_OK; LPWSTR sczTempPath = NULL; - DWORD cchTempPath = MAX_PATH; LPWSTR scz = NULL; @@ -813,13 +910,8 @@ DAPI_(HRESULT) PathCreateTempDirectory( } else { - hr = StrAlloc(&sczTempPath, cchTempPath); - PathExitOnFailure(hr, "Failed to allocate memory for the temp path."); - - if (!::GetTempPathW(cchTempPath, sczTempPath)) - { - PathExitWithLastError(hr, "Failed to get temp path."); - } + hr = PathGetTempPath(&sczTempPath, NULL); + PathExitOnFailure(hr, "Failed to get temp path."); } for (DWORD i = 1; i <= dwUniqueCount; ++i) @@ -869,46 +961,230 @@ LExit: DAPI_(HRESULT) PathGetTempPath( - __out_z LPWSTR* psczTempPath + __out_z LPWSTR* psczTempPath, + __out_opt SIZE_T* pcch + ) +{ + + HRESULT hr = S_OK; + SIZE_T cchMax = 0; + DWORD cchBuffer = 0; + DWORD cch = 0; + DWORD dwAttempts = 0; + HMODULE hModule = NULL; + PFN_GETTEMPPATH2W pfnGetTempPath = NULL; + const DWORD dwMaxAttempts = 10; + + if (*psczTempPath) + { + hr = StrMaxLength(*psczTempPath, &cchMax); + PathExitOnFailure(hr, "Failed to get max length of input buffer."); + + cchBuffer = (DWORD)min(DWORD_MAX, cchMax); + } + else + { + cchBuffer = MAX_PATH + 1; + + hr = StrAlloc(psczTempPath, cchBuffer); + PathExitOnFailure(hr, "Failed to allocate space for temp path."); + } + + hr = LoadSystemLibrary(L"kernel32.dll", &hModule); + PathExitOnFailure(hr, "Failed to load kernel32.dll"); + + pfnGetTempPath = reinterpret_cast(::GetProcAddress(hModule, "GetTempPath2W")); + if (!pfnGetTempPath) + { + pfnGetTempPath = ::GetTempPathW; + } + + for (; dwAttempts < dwMaxAttempts; ++dwAttempts) + { + cch = pfnGetTempPath(cchBuffer, *psczTempPath); + PathExitOnNullWithLastError(cch, hr, "Failed to get temp path."); + + cch += 1; // add one for null terminator. + + if (cch <= cchBuffer) + { + break; + } + + hr = StrAlloc(psczTempPath, cch); + PathExitOnFailure(hr, "Failed to reallocate space for temp path."); + + cchBuffer = cch; + } + + if (dwMaxAttempts == dwAttempts) + { + PathExitWithRootFailure(hr, E_INSUFFICIENT_BUFFER, "GetTempPathW results never converged."); + } + + if (pcch) + { + *pcch = cch - 1; // remove one for null terminator. + } + +LExit: + return hr; +} + + +DAPI_(HRESULT) PathGetSystemDirectory( + __out_z LPWSTR* psczSystemPath + ) +{ + HRESULT hr = S_OK; + SIZE_T cchMax = 0; + DWORD cchBuffer = 0; + DWORD cch = 0; + + if (*psczSystemPath) + { + hr = StrMaxLength(*psczSystemPath, &cchMax); + PathExitOnFailure(hr, "Failed to get max length of input buffer."); + + cchBuffer = (DWORD)min(DWORD_MAX, cchMax); + } + else + { + cchBuffer = MAX_PATH + 1; + + hr = StrAlloc(psczSystemPath, cchBuffer); + PathExitOnFailure(hr, "Failed to allocate space for system directory."); + } + + cch = ::GetSystemDirectoryW(*psczSystemPath, cchBuffer); + PathExitOnNullWithLastError(cch, hr, "Failed to get system directory path with default size."); + + if (cch > cchBuffer) + { + hr = StrAlloc(psczSystemPath, cch); + PathExitOnFailure(hr, "Failed to realloc system directory path."); + + cchBuffer = cch; + + cch = ::GetSystemDirectoryW(*psczSystemPath, cchBuffer); + PathExitOnNullWithLastError(cch, hr, "Failed to get system directory path with returned size."); + + if (cch > cchBuffer) + { + PathExitWithRootFailure(hr, E_INSUFFICIENT_BUFFER, "Failed to get system directory path with returned size."); + } + } + + hr = PathBackslashTerminate(psczSystemPath); + PathExitOnFailure(hr, "Failed to terminate system directory path with backslash."); + +LExit: + return hr; +} + + +DAPI_(HRESULT) PathGetSystemWow64Directory( + __out_z LPWSTR* psczSystemPath ) { HRESULT hr = S_OK; - WCHAR wzTempPath[MAX_PATH + 1] = { }; + SIZE_T cchMax = 0; + DWORD cchBuffer = 0; DWORD cch = 0; - cch = ::GetTempPathW(countof(wzTempPath), wzTempPath); - if (!cch) + if (*psczSystemPath) { - PathExitWithLastError(hr, "Failed to GetTempPath."); + hr = StrMaxLength(*psczSystemPath, &cchMax); + PathExitOnFailure(hr, "Failed to get max length of input buffer."); + + cchBuffer = (DWORD)min(DWORD_MAX, cchMax); } - else if (cch >= countof(wzTempPath)) + else { - PathExitWithRootFailure(hr, E_INSUFFICIENT_BUFFER, "TEMP directory path too long."); + cchBuffer = MAX_PATH + 1; + + hr = StrAlloc(psczSystemPath, cchBuffer); + PathExitOnFailure(hr, "Failed to allocate space for system wow64 directory."); } - hr = StrAllocString(psczTempPath, wzTempPath, cch); - PathExitOnFailure(hr, "Failed to copy TEMP directory path."); + cch = ::GetSystemWow64DirectoryW(*psczSystemPath, cchBuffer); + PathExitOnNullWithLastError(cch, hr, "Failed to get system wow64 directory path with default size."); + + cch += 1; // add one for the null terminator. + + if (cch > cchBuffer) + { + hr = StrAlloc(psczSystemPath, cch); + PathExitOnFailure(hr, "Failed to realloc system wow64 directory path."); + + cchBuffer = cch; + + cch = ::GetSystemWow64DirectoryW(*psczSystemPath, cchBuffer); + PathExitOnNullWithLastError(cch, hr, "Failed to get system wow64 directory path with returned size."); + + cch += 1; // add one for the null terminator. + + if (cch > cchBuffer) + { + PathExitWithRootFailure(hr, E_INSUFFICIENT_BUFFER, "Failed to get system wow64 directory path with returned size."); + } + } + + hr = PathBackslashTerminate(psczSystemPath); + PathExitOnFailure(hr, "Failed to terminate system wow64 directory path with backslash."); LExit: return hr; } -DAPI_(HRESULT) PathGetKnownFolder( - __in int csidl, - __out LPWSTR* psczKnownFolder +DAPI_(HRESULT) PathGetVolumePathName( + __in_z LPCWSTR wzFileName, + __out_z LPWSTR* psczVolumePathName ) { HRESULT hr = S_OK; + DWORD cchBuffer = 0; + SIZE_T cchMax = 0; + const DWORD dwMaxAttempts = 20; - hr = StrAlloc(psczKnownFolder, MAX_PATH); - PathExitOnFailure(hr, "Failed to allocate memory for known folder."); + if (*psczVolumePathName) + { + hr = StrMaxLength(*psczVolumePathName, &cchMax); + PathExitOnFailure(hr, "Failed to get max length of input buffer."); - hr = ::SHGetFolderPathW(NULL, csidl, NULL, SHGFP_TYPE_CURRENT, *psczKnownFolder); - PathExitOnFailure(hr, "Failed to get known folder path."); + cchBuffer = (DWORD)min(DWORD_MAX, cchMax); + } + else + { + cchBuffer = MAX_PATH + 1; + + hr = StrAlloc(psczVolumePathName, cchBuffer); + PathExitOnFailure(hr, "Failed to allocate space for volume path name."); + } + + for (DWORD i = 0; i < dwMaxAttempts; ++i) + { + if (::GetVolumePathNameW(wzFileName, *psczVolumePathName, cchBuffer)) + { + break; + } + + hr = HRESULT_FROM_WIN32(::GetLastError()); + if (HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) != hr && E_INSUFFICIENT_BUFFER != hr || + (dwMaxAttempts - 1) == i) + { + PathExitWithRootFailure(hr, hr, "Failed to get volume path name of: %ls", wzFileName); + } + + cchBuffer *= 2; + + hr = StrAlloc(psczVolumePathName, cchBuffer); + PathExitOnFailure(hr, "Failed to re-allocate more space for volume path name."); + } - hr = PathBackslashTerminate(psczKnownFolder); - PathExitOnFailure(hr, "Failed to ensure known folder path is backslash terminated."); + hr = PathBackslashTerminate(psczVolumePathName); + PathExitOnFailure(hr, "Failed to terminate volume path name with backslash."); LExit: return hr; diff --git a/src/libs/dutil/WixToolset.DUtil/rexutil.cpp b/src/libs/dutil/WixToolset.DUtil/rexutil.cpp index 155ca714..ce28beb3 100644 --- a/src/libs/dutil/WixToolset.DUtil/rexutil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/rexutil.cpp @@ -28,7 +28,7 @@ static ERF verf; static FAKE_FILE vrgffFileTable[FILETABLESIZE]; static DWORD vcbRes; static LPCBYTE vpbRes; -static CHAR vszResource[MAX_PATH]; +static LPSTR vpszResource = NULL; static REX_CALLBACK_WRITE vpfnWrite = NULL; static HRESULT vhrLastError = S_OK; @@ -85,6 +85,8 @@ LExit: { ::FDIDestroy(vhfdi); vhfdi = NULL; + + ReleaseNullStr(vpszResource); } return hr; @@ -101,6 +103,8 @@ extern "C" void RexUninitialize() { ::FDIDestroy(vhfdi); vhfdi = NULL; + + ReleaseNullStr(vpszResource); } } @@ -124,12 +128,12 @@ extern "C" HRESULT RexExtract( { Assert(vhfdi); HRESULT hr = S_OK; - BOOL fResult; + BOOL fResult = FALSE; HRSRC hResInfo = NULL; HANDLE hRes = NULL; - REX_CALLBACK_STRUCT rcs; + REX_CALLBACK_STRUCT rcs = { }; // remember the write callback vpfnWrite = pfnWrite; @@ -158,7 +162,7 @@ extern "C" HRESULT RexExtract( // RexExitOnLastError(hr, "failed to convert cabinet resource name to ASCII: %ls", wzResource); //} - hr = ::StringCchCopyA(vszResource, countof(vszResource), szResource); + hr = StrAnsiAllocStringAnsi(&vpszResource, szResource, 0); RexExitOnFailure(hr, "Failed to copy resource name to global."); // @@ -171,7 +175,7 @@ extern "C" HRESULT RexExtract( rcs.pfnProgress = pfnProgress; rcs.pvContext = pvContext; - fResult = ::FDICopy(vhfdi, vszResource, "", 0, RexCallback, NULL, static_cast(&rcs)); + fResult = ::FDICopy(vhfdi, vpszResource, "", 0, RexCallback, NULL, static_cast(&rcs)); if (!fResult && !rcs.fStopExtracting) // if something went wrong and it wasn't us just stopping the extraction, then return a failure { hr = vhrLastError; // TODO: put verf info in trace message here @@ -227,7 +231,7 @@ static __callback INT_PTR FAR DIAMONDAPI RexOpen(__in_z char FAR *pszFile, int o RexExitOnFailure(hr, "File table exceeded"); } - if (0 == lstrcmpA(vszResource, pszFile)) + if (0 == lstrcmpA(vpszResource, pszFile)) { vrgffFileTable[i].fUsed = TRUE; vrgffFileTable[i].fftType = MEMORY_FILE; @@ -436,15 +440,16 @@ static __callback INT_PTR DIAMONDAPI RexCallback(FDINOTIFICATIONTYPE iNotificati HANDLE hFile = INVALID_HANDLE_VALUE; REX_CALLBACK_STRUCT* prcs = static_cast(pFDINotify->pv); - LPCSTR sz; - WCHAR wz[MAX_PATH]; - FILETIME ft; + LPCSTR sz = NULL; + LPWSTR pwz = NULL; + LPWSTR pwzPath = NULL; + FILETIME ft = { }; int i = 0; switch (iNotification) { case fdintCOPY_FILE: // beGIN extracting a resource from cabinet - Assert(pFDINotify->psz1); + Assert(pFDINotify->psz1 && prcs); if (prcs->fStopExtracting) { @@ -453,55 +458,50 @@ static __callback INT_PTR DIAMONDAPI RexCallback(FDINOTIFICATIONTYPE iNotificati // convert params to useful variables sz = static_cast(pFDINotify->psz1); - if (!::MultiByteToWideChar(CP_ACP, 0, sz, -1, wz, countof(wz))) - { - RexExitWithLastError(hr, "failed to convert cabinet file id to unicode: %s", sz); - } + RexExitOnNull(sz, hr, E_INVALIDARG, "No cabinet file ID given to convert"); + + hr = StrAllocStringAnsi(&pwz, sz, 0, CP_ACP); + RexExitOnFailure(hr, "failed to convert cabinet file id to unicode: %hs", sz); if (prcs->pfnProgress) { - hr = prcs->pfnProgress(TRUE, wz, prcs->pvContext); + hr = prcs->pfnProgress(TRUE, pwz, prcs->pvContext); if (S_OK != hr) { ExitFunction(); } } - if (L'*' == *prcs->pwzExtract || 0 == lstrcmpW(prcs->pwzExtract, wz)) + if (L'*' == *prcs->pwzExtract || 0 == lstrcmpW(prcs->pwzExtract, pwz)) { // get the created date for the resource in the cabinet if (!::DosDateTimeToFileTime(pFDINotify->date, pFDINotify->time, &ft)) { - RexExitWithLastError(hr, "failed to get time for resource: %ls", wz); + RexExitWithLastError(hr, "failed to get time for resource: %ls", pwz); } - WCHAR wzPath[MAX_PATH]; - - hr = ::StringCchCopyW(wzPath, countof(wzPath), prcs->pwzExtractDir); - RexExitOnFailure(hr, "failed to copy extract directory: %ls for file: %ls", prcs->pwzExtractDir, wz); - if (L'*' == *prcs->pwzExtract) { - hr = ::StringCchCatW(wzPath, countof(wzPath), wz); - RexExitOnFailure(hr, "failed to concat onto path: %ls file: %ls", wzPath, wz); + hr = PathConcat(prcs->pwzExtractDir, pwz, &pwzPath); + RexExitOnFailure(hr, "failed to concat onto path: %ls file: %ls", prcs->pwzExtractDir, pwz); } else { Assert(*prcs->pwzExtractName); - hr = ::StringCchCatW(wzPath, countof(wzPath), prcs->pwzExtractName); - RexExitOnFailure(hr, "failed to concat onto path: %ls file: %ls", wzPath, prcs->pwzExtractName); + hr = PathConcat(prcs->pwzExtractDir, prcs->pwzExtractName, &pwzPath); + RexExitOnFailure(hr, "failed to concat onto path: %ls file: %ls", prcs->pwzExtractDir, prcs->pwzExtractName); } // Quickly chop off the file name part of the path to ensure the path exists // then put the file name back on the path (by putting the first character // back over the null terminator). - LPWSTR wzFile = PathFile(wzPath); + LPWSTR wzFile = PathFile(pwzPath); WCHAR wzFileFirstChar = *wzFile; *wzFile = L'\0'; - hr = DirEnsureExists(wzPath, NULL); - RexExitOnFailure(hr, "failed to ensure directory: %ls", wzPath); + hr = DirEnsureExists(pwzPath, NULL); + RexExitOnFailure(hr, "failed to ensure directory: %ls", pwzPath); hr = S_OK; @@ -524,10 +524,10 @@ static __callback INT_PTR DIAMONDAPI RexCallback(FDINOTIFICATIONTYPE iNotificati } // open the file - hFile = ::CreateFileW(wzPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + hFile = ::CreateFileW(pwzPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hFile) { - RexExitWithLastError(hr, "failed to open file: %ls", wzPath); + RexExitWithLastError(hr, "failed to open file: %ls", pwzPath); } vrgffFileTable[i].fUsed = TRUE; @@ -554,20 +554,20 @@ static __callback INT_PTR DIAMONDAPI RexCallback(FDINOTIFICATIONTYPE iNotificati break; case fdintCLOSE_FILE_INFO: // resource extraction complete - Assert(pFDINotify->hf && pFDINotify->psz1); + Assert(pFDINotify->hf && prcs && pFDINotify->psz1); // convert params to useful variables sz = static_cast(pFDINotify->psz1); - if (!::MultiByteToWideChar(CP_ACP, 0, sz, -1, wz, countof(wz))) - { - RexExitWithLastError(hr, "failed to convert cabinet file id to unicode: %s", sz); - } + RexExitOnNull(sz, hr, E_INVALIDARG, "No cabinet file ID given to convert"); + + hr = StrAllocStringAnsi(&pwz, sz, 0, CP_ACP); + RexExitOnFailure(hr, "failed to convert cabinet file id to unicode: %hs", sz); RexClose(pFDINotify->hf); if (prcs->pfnProgress) { - hr = prcs->pfnProgress(FALSE, wz, prcs->pvContext); + hr = prcs->pfnProgress(FALSE, pwz, prcs->pvContext); } if (S_OK == hr && L'*' == *prcs->pwzExtract) // if everything is okay and we're extracting all files, keep going @@ -597,5 +597,8 @@ LExit: vhrLastError = hr; } + ReleaseStr(pwz); + ReleaseStr(pwzPath); + return (S_OK == hr) ? ipResult : -1; } diff --git a/src/libs/dutil/WixToolset.DUtil/sceutil.cpp b/src/libs/dutil/WixToolset.DUtil/sceutil.cpp index 4eede74f..590c937a 100644 --- a/src/libs/dutil/WixToolset.DUtil/sceutil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/sceutil.cpp @@ -317,7 +317,7 @@ extern "C" HRESULT DAPI SceOpenDatabase( { HRESULT hr = S_OK; DWORD dwVersionFound = 0; - WCHAR wzTempDbFile[MAX_PATH]; + LPWSTR sczTempDbFile = NULL; LPCWSTR wzPathToOpen = NULL; LPWSTR sczSchemaType = NULL; SCE_DATABASE *pNewSceDatabase = NULL; @@ -343,16 +343,16 @@ extern "C" HRESULT DAPI SceOpenDatabase( // TODO: had trouble getting SQL CE to read a file read-only, so we're copying it to a temp path for now. if (fReadOnly) { - hr = DirCreateTempPath(PathFile(sczFile), (LPWSTR)wzTempDbFile, _countof(wzTempDbFile)); + hr = DirCreateTempPath(PathFile(sczFile), &sczTempDbFile); ExitOnFailure(hr, "Failed to get temp path"); - hr = FileEnsureCopy(sczFile, (LPCWSTR)wzTempDbFile, TRUE); + hr = FileEnsureCopy(sczFile, sczTempDbFile, TRUE); ExitOnFailure(hr, "Failed to copy file to temp path"); - hr = StrAllocString(&pNewSceDatabaseInternal->sczTempDbFile, (LPCWSTR)wzTempDbFile, 0); + hr = StrAllocString(&pNewSceDatabaseInternal->sczTempDbFile, sczTempDbFile, 0); ExitOnFailure(hr, "Failed to copy temp db file path"); - wzPathToOpen = (LPCWSTR)wzTempDbFile; + wzPathToOpen = sczTempDbFile; } else { @@ -424,6 +424,7 @@ extern "C" HRESULT DAPI SceOpenDatabase( LExit: ReleaseBSTR(rgdbpDataSourceProp[0].vValue.bstrVal); ReleaseStr(sczSchemaType); + ReleaseStr(sczTempDbFile); ReleaseDatabase(pNewSceDatabase); return hr; diff --git a/src/libs/dutil/WixToolset.DUtil/shelutil.cpp b/src/libs/dutil/WixToolset.DUtil/shelutil.cpp index 2eb9a52a..72a0e5ce 100644 --- a/src/libs/dutil/WixToolset.DUtil/shelutil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/shelutil.cpp @@ -9,6 +9,7 @@ #define ShelExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_SHELUTIL, x, s, __VA_ARGS__) #define ShelExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_SHELUTIL, x, s, __VA_ARGS__) #define ShelExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_SHELUTIL, x, s, __VA_ARGS__) +#define ShelExitWithRootFailure(x, e, s, ...) ExitWithRootFailureSource(DUTIL_SOURCE_SHELUTIL, x, e, s, __VA_ARGS__) #define ShelExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_SHELUTIL, x, s, __VA_ARGS__) #define ShelExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_SHELUTIL, p, x, e, s, __VA_ARGS__) #define ShelExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_SHELUTIL, p, x, s, __VA_ARGS__) @@ -19,6 +20,10 @@ static PFN_SHELLEXECUTEEXW vpfnShellExecuteExW = ::ShellExecuteExW; +static HRESULT DAPI GetFolderFromCsidl( + __out_z LPWSTR* psczFolderPath, + __in int csidlFolder + ); static HRESULT GetDesktopShellView( __in REFIID riid, __out void **ppv @@ -57,7 +62,14 @@ extern "C" HRESULT DAPI ShelExec( ) { HRESULT hr = S_OK; - SHELLEXECUTEINFOW shExecInfo = {}; + SHELLEXECUTEINFOW shExecInfo = { }; + size_t cchWorkingDirectory = 0; + + // CreateProcessW has undocumented MAX_PATH restriction for lpCurrentDirectory even when long path support is enabled. + if (wzWorkingDirectory && FAILED(::StringCchLengthW(wzWorkingDirectory, MAX_PATH - 1, &cchWorkingDirectory))) + { + wzWorkingDirectory = NULL; + } shExecInfo.cbSize = sizeof(SHELLEXECUTEINFO); shExecInfo.fMask = SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS; @@ -159,11 +171,7 @@ LExit: } -/******************************************************************** - ShelGetFolder() - gets a folder by CSIDL. - -*******************************************************************/ -extern "C" HRESULT DAPI ShelGetFolder( +static HRESULT DAPI GetFolderFromCsidl( __out_z LPWSTR* psczFolderPath, __in int csidlFolder ) @@ -185,19 +193,6 @@ LExit: } -/******************************************************************** - ShelGetKnownFolder() - gets a folder by KNOWNFOLDERID. - - Note: return E_NOTIMPL if called on pre-Vista operating systems. -*******************************************************************/ -#ifndef REFKNOWNFOLDERID -#define REFKNOWNFOLDERID REFGUID -#endif - -#ifndef KF_FLAG_CREATE -#define KF_FLAG_CREATE 0x00008000 // Make sure that the folder already exists or create it and apply security specified in folder definition -#endif - EXTERN_C typedef HRESULT (STDAPICALLTYPE *PFN_SHGetKnownFolderPath)( REFKNOWNFOLDERID rfid, DWORD dwFlags, @@ -249,6 +244,181 @@ LExit: return hr; } +extern "C" HRESULT DAPI ShelGetFolder( + __out_z LPWSTR* psczFolderPath, + __in int csidlFolder + ) +{ + HRESULT hr = S_OK; + LPWSTR sczPath = NULL; + KNOWNFOLDERID rfid = { }; + + csidlFolder &= ~CSIDL_FLAG_MASK; + + switch (csidlFolder) + { + case CSIDL_ADMINTOOLS: + rfid = FOLDERID_AdminTools; + break; + case CSIDL_APPDATA: + rfid = FOLDERID_RoamingAppData; + break; + case CSIDL_CDBURN_AREA: + rfid = FOLDERID_CDBurning; + break; + case CSIDL_COMMON_ADMINTOOLS: + rfid = FOLDERID_CommonAdminTools; + break; + case CSIDL_COMMON_APPDATA: + rfid = FOLDERID_ProgramData; + break; + case CSIDL_COMMON_DESKTOPDIRECTORY: + rfid = FOLDERID_PublicDesktop; + break; + case CSIDL_COMMON_DOCUMENTS: + rfid = FOLDERID_PublicDocuments; + break; + case CSIDL_COMMON_MUSIC: + rfid = FOLDERID_PublicMusic; + break; + case CSIDL_COMMON_OEM_LINKS: + rfid = FOLDERID_CommonOEMLinks; + break; + case CSIDL_COMMON_PICTURES: + rfid = FOLDERID_PublicPictures; + break; + case CSIDL_COMMON_PROGRAMS: + rfid = FOLDERID_CommonPrograms; + break; + case CSIDL_COMMON_STARTMENU: + rfid = FOLDERID_CommonStartMenu; + break; + case CSIDL_COMMON_STARTUP: __fallthrough; + case CSIDL_COMMON_ALTSTARTUP: + rfid = FOLDERID_CommonStartup; + break; + case CSIDL_COMMON_TEMPLATES: + rfid = FOLDERID_CommonTemplates; + break; + case CSIDL_COMMON_VIDEO: + rfid = FOLDERID_PublicVideos; + break; + case CSIDL_COOKIES: + rfid = FOLDERID_Cookies; + break; + case CSIDL_DESKTOP: + case CSIDL_DESKTOPDIRECTORY: + rfid = FOLDERID_Desktop; + break; + case CSIDL_FAVORITES: __fallthrough; + case CSIDL_COMMON_FAVORITES: + rfid = FOLDERID_Favorites; + break; + case CSIDL_FONTS: + rfid = FOLDERID_Fonts; + break; + case CSIDL_HISTORY: + rfid = FOLDERID_History; + break; + case CSIDL_INTERNET_CACHE: + rfid = FOLDERID_InternetCache; + break; + case CSIDL_LOCAL_APPDATA: + rfid = FOLDERID_LocalAppData; + break; + case CSIDL_MYMUSIC: + rfid = FOLDERID_Music; + break; + case CSIDL_MYPICTURES: + rfid = FOLDERID_Pictures; + break; + case CSIDL_MYVIDEO: + rfid = FOLDERID_Videos; + break; + case CSIDL_NETHOOD: + rfid = FOLDERID_NetHood; + break; + case CSIDL_PERSONAL: + rfid = FOLDERID_Documents; + break; + case CSIDL_PRINTHOOD: + rfid = FOLDERID_PrintHood; + break; + case CSIDL_PROFILE: + rfid = FOLDERID_Profile; + break; + case CSIDL_PROGRAM_FILES: + rfid = FOLDERID_ProgramFiles; + break; + case CSIDL_PROGRAM_FILESX86: + rfid = FOLDERID_ProgramFilesX86; + break; + case CSIDL_PROGRAM_FILES_COMMON: + rfid = FOLDERID_ProgramFilesCommon; + break; + case CSIDL_PROGRAM_FILES_COMMONX86: + rfid = FOLDERID_ProgramFilesCommonX86; + break; + case CSIDL_PROGRAMS: + rfid = FOLDERID_Programs; + break; + case CSIDL_RECENT: + rfid = FOLDERID_Recent; + break; + case CSIDL_RESOURCES: + rfid = FOLDERID_ResourceDir; + break; + case CSIDL_RESOURCES_LOCALIZED: + rfid = FOLDERID_LocalizedResourcesDir; + break; + case CSIDL_SENDTO: + rfid = FOLDERID_SendTo; + break; + case CSIDL_STARTMENU: + rfid = FOLDERID_StartMenu; + break; + case CSIDL_STARTUP: + case CSIDL_ALTSTARTUP: + rfid = FOLDERID_Startup; + break; + case CSIDL_SYSTEM: + rfid = FOLDERID_System; + break; + case CSIDL_SYSTEMX86: + rfid = FOLDERID_SystemX86; + break; + case CSIDL_TEMPLATES: + rfid = FOLDERID_Templates; + break; + case CSIDL_WINDOWS: + rfid = FOLDERID_Windows; + break; + default: + ShelExitWithRootFailure(hr, E_INVALIDARG, "Unknown csidl: %d", csidlFolder); + } + + hr = ShelGetKnownFolder(&sczPath, rfid); + if (E_NOTIMPL == hr) + { + hr = S_FALSE; + } + ShelExitOnFailure(hr, "Failed to get known folder."); + + if (S_FALSE == hr) + { + hr = GetFolderFromCsidl(&sczPath, csidlFolder); + ShelExitOnFailure(hr, "Failed to get csidl folder."); + } + + *psczFolderPath = sczPath; + sczPath = NULL; + +LExit: + ReleaseStr(sczPath); + + return hr; +} + // Internal functions. @@ -287,7 +457,7 @@ static HRESULT GetDesktopShellView( else if (S_FALSE == hr) { //Windows XP - hr = SHGetDesktopFolder(&psf); + hr = ::SHGetDesktopFolder(&psf); ShelExitOnFailure(hr, "Failed to get desktop folder."); hr = psf->CreateViewObject(NULL, IID_IShellView, ppv); diff --git a/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp index e9ef1047..109c558c 100644 --- a/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp +++ b/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp @@ -7,6 +7,11 @@ using namespace System::IO; using namespace Xunit; using namespace WixBuildTools::TestSupport; +// from PathCch.h +#ifndef PATHCCH_ALLOW_LONG_PATHS +#define PATHCCH_ALLOW_LONG_PATHS 0x01 +#endif + namespace DutilTests { public ref class PathUtil @@ -101,8 +106,28 @@ namespace DutilTests } } + [Fact] + void PathCanonicalizeForComparisonFallbackTest() + { + Path2FunctionForceFallback(); + + try + { + PathCanonicalizeForComparisonTestCore(FALSE); + } + finally + { + Path2FunctionAllowFallback(); + } + } + [Fact] void PathCanonicalizeForComparisonTest() + { + PathCanonicalizeForComparisonTestCore(TRUE); + } + + void PathCanonicalizeForComparisonTestCore(BOOL fLongPathSupported) { HRESULT hr = S_OK; LPWSTR sczCanonicalized = NULL; @@ -110,10 +135,26 @@ namespace DutilTests try { hr = PathCanonicalizeForComparison(L"C:\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789", 0, &sczCanonicalized); - Assert::Equal(HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE), hr); + if (!fLongPathSupported) + { + Assert::Equal(HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE), hr); + } + else + { + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"\\\\?\\C:\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789", sczCanonicalized); + } hr = PathCanonicalizeForComparison(L"\\\\?\\C:\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789", 0, &sczCanonicalized); - Assert::Equal(HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE), hr); + if (!fLongPathSupported) + { + Assert::Equal(HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE), hr); + } + else + { + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"\\\\?\\C:\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789", sczCanonicalized); + } hr = PathCanonicalizeForComparison(L"\\\\server", PATH_CANONICALIZE_KEEP_UNC_ROOT, &sczCanonicalized); NativeAssert::Succeeded(hr, "Failed to canonicalize path"); @@ -237,6 +278,52 @@ namespace DutilTests } } + [Fact] + void PathAllocCanonicalizePathTest() + { + HRESULT hr = S_OK; + LPWSTR sczCanonicalized = NULL; + + try + { + hr = PathAllocCanonicalizePath(L"C:\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789", PATHCCH_ALLOW_LONG_PATHS, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"\\\\?\\C:\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789", sczCanonicalized); + + hr = PathAllocCanonicalizePath(L"abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789", PATHCCH_ALLOW_LONG_PATHS, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789\\abcdefghijklomnopqrstuvwxyz0123456789", sczCanonicalized); + + hr = PathAllocCanonicalizePath(L"\\\\server\\share\\..\\..\\otherdir\\unc.exe", PATHCCH_ALLOW_LONG_PATHS, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"\\\\otherdir\\unc.exe", sczCanonicalized); // This is surprising. + + hr = PathAllocCanonicalizePath(L"C:\\dir\\subdir\\..\\..\\otherdir\\backslashes.exe", PATHCCH_ALLOW_LONG_PATHS, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"C:\\otherdir\\backslashes.exe", sczCanonicalized); + + hr = PathAllocCanonicalizePath(L"C:/dir/subdir/../../otherdir/forwardslashes.exe", PATHCCH_ALLOW_LONG_PATHS, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"C:/dir/subdir/../../otherdir/forwardslashes.exe", sczCanonicalized); + + hr = PathAllocCanonicalizePath(L"\\\\?\\C:\\test\\..\\validlongpath.exe", PATHCCH_ALLOW_LONG_PATHS, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"C:\\validlongpath.exe", sczCanonicalized); + + hr = PathAllocCanonicalizePath(L"\\\\?\\test\\..\\invalidlongpath.exe", PATHCCH_ALLOW_LONG_PATHS, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"\\\\?\\invalidlongpath.exe", sczCanonicalized); + + hr = PathAllocCanonicalizePath(L"C:\\.\\invalid:pathchars?.exe", PATHCCH_ALLOW_LONG_PATHS, &sczCanonicalized); + NativeAssert::Succeeded(hr, "Failed to canonicalize path"); + NativeAssert::StringEqual(L"C:\\invalid:pathchars?.exe", sczCanonicalized); + } + finally + { + ReleaseStr(sczCanonicalized); + } + } + [Fact] void PathConcatTest() { @@ -544,6 +631,25 @@ namespace DutilTests } } + [Fact] + void PathForCurrentProcessTest() + { + HRESULT hr = S_OK; + LPWSTR sczPath = NULL; + + try + { + hr = PathForCurrentProcess(&sczPath, NULL); + NativeAssert::Succeeded(hr, "Failed to get current process path."); + + WixAssert::StringEqual(System::Diagnostics::Process::GetCurrentProcess()->MainModule->FileName, gcnew String(sczPath), false); + } + finally + { + ReleaseStr(sczPath); + } + } + [Fact] void PathGetDirectoryTest() { @@ -827,6 +933,78 @@ namespace DutilTests } } + [Fact] + void PathGetTempPathTest() + { + HRESULT hr = S_OK; + LPCWSTR wzEnvName = L"TMP"; + LPCWSTR wzEnvName2 = L"TEMP"; + LPCWSTR wzLongTempPath = L"C:\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\\"; + LPWSTR sczTempPath = NULL; + WCHAR wzOriginalTemp[MAX_PATH + 1] = { }; + WCHAR wzOriginalTemp2[MAX_PATH + 1] = { }; + DWORD cch = 0; + DWORD cch2 = 0; + SIZE_T cchTemp = 0; + size_t cchTemp2 = 0; + + try + { + cch = ::GetEnvironmentVariableW(wzEnvName, wzOriginalTemp, countof(wzOriginalTemp)); + Assert::NotEqual(0, cch); + + if (!::SetEnvironmentVariableW(wzEnvName, wzLongTempPath)) + { + Assert::Equal(0xFFFFFFFF, ::GetLastError()); + } + + cch2 = ::GetEnvironmentVariableW(wzEnvName2, wzOriginalTemp2, countof(wzOriginalTemp2)); + Assert::NotEqual(0, cch2); + + hr = PathGetTempPath(&sczTempPath, &cchTemp); + NativeAssert::Succeeded(hr, "Failed to get temp path."); + + PathFixedBackslashTerminate(wzOriginalTemp2, countof(wzOriginalTemp2)); + + hr = ::StringCchLengthW(wzOriginalTemp2, countof(wzOriginalTemp2), &cchTemp2); + NativeAssert::Succeeded(hr, "Failed to get temp path length."); + + NativeAssert::StringEqual(wzOriginalTemp2, sczTempPath); + Assert::Equal(cchTemp2, cchTemp); + } + finally + { + if (cch) + { + ::SetEnvironmentVariableW(wzEnvName, wzOriginalTemp); + } + + ReleaseStr(sczTempPath); + } + } + + [Fact] + void PathGetVolumePathNameTest() + { + HRESULT hr = S_OK; + LPWSTR sczVolumePathName = NULL; + + try + { + hr = StrAlloc(&sczVolumePathName, 1); + NativeAssert::Succeeded(hr, "Failed to alloc volume path name."); + + hr = PathGetVolumePathName(L"C:\\Windows", &sczVolumePathName); + NativeAssert::Succeeded(hr, "PathGetVolumePathName failed."); + + NativeAssert::StringEqual(L"C:\\", sczVolumePathName); + } + finally + { + ReleaseStr(sczVolumePathName); + } + } + [Fact] void PathNormalizeSlashesFixedTest() { diff --git a/src/test/burn/Directory.wixproj.targets b/src/test/burn/Directory.wixproj.targets index 17a46e2a..4037e865 100644 --- a/src/test/burn/Directory.wixproj.targets +++ b/src/test/burn/Directory.wixproj.targets @@ -7,6 +7,7 @@ http://localhost:9999/e2e/ TestGroupName=$(TestGroupName);PackageName=$(PackageName);BundleName=$(BundleName);WebServerBaseUrl=$(WebServerBaseUrl);$(DefineConstants) BA=$(BA);$(DefineConstants) + BundleLogDirectory=$(BundleLogDirectory);$(DefineConstants) CabPrefix=$(CabPrefix);$(DefineConstants) SoftwareTag=1;$(DefineConstants) ProductCode=$(ProductCode);$(DefineConstants) diff --git a/src/test/burn/TestData/LongPathTests/NonCompressedBundle/NonCompressedBundle.wixproj b/src/test/burn/TestData/LongPathTests/NonCompressedBundle/NonCompressedBundle.wixproj new file mode 100644 index 00000000..3686200c --- /dev/null +++ b/src/test/burn/TestData/LongPathTests/NonCompressedBundle/NonCompressedBundle.wixproj @@ -0,0 +1,19 @@ + + + + Bundle + {EFCF768F-1B06-4B68-9DE0-9244F8212D31} + [LocalAppDataFolder]Temp + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/LongPathTests/NonCompressedBundle/NonCompressedBundle.wxs b/src/test/burn/TestData/LongPathTests/NonCompressedBundle/NonCompressedBundle.wxs new file mode 100644 index 00000000..e3872eb1 --- /dev/null +++ b/src/test/burn/TestData/LongPathTests/NonCompressedBundle/NonCompressedBundle.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/burn/TestData/LongPathTests/PackageA/PackageA.wixproj b/src/test/burn/TestData/LongPathTests/PackageA/PackageA.wixproj new file mode 100644 index 00000000..798452e7 --- /dev/null +++ b/src/test/burn/TestData/LongPathTests/PackageA/PackageA.wixproj @@ -0,0 +1,10 @@ + + + + a + {4DC05A2A-382D-4E7D-B6DA-163908396373} + + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/Templates/Bundle.wxs b/src/test/burn/TestData/Templates/Bundle.wxs index b211d9c3..612e67f5 100644 --- a/src/test/burn/TestData/Templates/Bundle.wxs +++ b/src/test/burn/TestData/Templates/Bundle.wxs @@ -3,10 +3,13 @@ + + + - + diff --git a/src/test/burn/TestExe/Task.cs b/src/test/burn/TestExe/Task.cs index 59f774fb..0d283c6c 100644 --- a/src/test/burn/TestExe/Task.cs +++ b/src/test/burn/TestExe/Task.cs @@ -2,8 +2,10 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.IO; +using System.Runtime.InteropServices; using Microsoft.Win32; namespace TestExe @@ -151,6 +153,67 @@ namespace TestExe } } + public class DeleteManifestsTask : Task + { + public DeleteManifestsTask(string Data) : base(Data) { } + + public override void RunTask() + { + string filePath = System.Environment.ExpandEnvironmentVariables(this.data); + IntPtr type = new IntPtr(24); //RT_MANIFEST + IntPtr name = new IntPtr(1); //CREATEPROCESS_MANIFEST_RESOURCE_ID + DeleteResource(filePath, type, name, 1033); + } + + private static void DeleteResource(string filePath, IntPtr type, IntPtr name, ushort language, bool throwOnError = false) + { + bool discard = true; + IntPtr handle = BeginUpdateResourceW(filePath, false); + try + { + if (handle == IntPtr.Zero) + { + throw new Win32Exception(); + } + + if (!UpdateResourceW(handle, type, name, language, IntPtr.Zero, 0)) + { + throw new Win32Exception(); + } + + discard = false; + } + catch + { + if (throwOnError) + { + throw; + } + } + finally + { + if (handle != IntPtr.Zero) + { + if (!EndUpdateResourceW(handle, discard) && throwOnError) + { + throw new Win32Exception(); + } + } + } + } + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + private extern static IntPtr BeginUpdateResourceW(string fileName, [MarshalAs(UnmanagedType.Bool)] bool deleteExistingResources); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private extern static bool UpdateResourceW(IntPtr hUpdate, IntPtr type, IntPtr name, ushort language, IntPtr pData, uint cb); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private extern static bool EndUpdateResourceW(IntPtr hUpdate, [MarshalAs(UnmanagedType.Bool)] bool discard); + } + public class TaskParser { @@ -197,6 +260,10 @@ namespace TestExe t = new FileExistsTask(args[i + 1]); tasks.Add(t); break; + case "/dm": + t = new DeleteManifestsTask(args[i + 1]); + tasks.Add(t); + break; #if NET35 case "/pinfo": t = new ProcessInfoTask(args[i + 1]); diff --git a/src/test/burn/WixTestTools/BundleInstaller.cs b/src/test/burn/WixTestTools/BundleInstaller.cs index 5551d3c0..0f2cfa8f 100644 --- a/src/test/burn/WixTestTools/BundleInstaller.cs +++ b/src/test/burn/WixTestTools/BundleInstaller.cs @@ -28,6 +28,8 @@ namespace WixTestTools public int? AlternateExitCode { get; set; } + public string LogDirectory { get; set; } + public int? LastExitCode { get; set; } /// @@ -194,7 +196,7 @@ namespace WixTestTools sb.Append(" -quiet"); // Generate the log file name. - string logFile = Path.Combine(Path.GetTempPath(), String.Format("{0}_{1}_{2:yyyyMMddhhmmss}_{4}_{3}.log", this.TestGroupName, this.TestName, DateTime.UtcNow, Path.GetFileNameWithoutExtension(this.Bundle), mode)); + string logFile = Path.Combine(this.LogDirectory ?? Path.GetTempPath(), String.Format("{0}_{1}_{2:yyyyMMddhhmmss}_{4}_{3}.log", this.TestGroupName, this.TestName, DateTime.UtcNow, Path.GetFileNameWithoutExtension(this.Bundle), mode)); sb.AppendFormat(" -log \"{0}\"", logFile); // Set operation. diff --git a/src/test/burn/WixTestTools/MSIExec.cs b/src/test/burn/WixTestTools/MSIExec.cs index a10a48d6..5f57da7b 100644 --- a/src/test/burn/WixTestTools/MSIExec.cs +++ b/src/test/burn/WixTestTools/MSIExec.cs @@ -110,7 +110,7 @@ namespace WixTestTools this.NoRestart = true; this.ForceRestart = false; this.PromptRestart = false; - this.LogFile = string.Empty; + this.LogFile = String.Empty; this.LoggingOptions = MSIExecLoggingOptions.VOICEWARMUP; this.OtherArguments = String.Empty; } @@ -230,14 +230,14 @@ namespace WixTestTools } // logfile and logging options - if (0 != loggingOptionsString.Length || !string.IsNullOrEmpty(this.LogFile)) + if (0 != loggingOptionsString.Length || !String.IsNullOrEmpty(this.LogFile)) { arguments.Append(" /l"); if (0 != loggingOptionsString.Length) { arguments.AppendFormat("{0} ", loggingOptionsString); } - if (!string.IsNullOrEmpty(this.LogFile)) + if (!String.IsNullOrEmpty(this.LogFile)) { arguments.AppendFormat(" \"{0}\" ", this.LogFile); } @@ -268,7 +268,7 @@ namespace WixTestTools }; // product - if (!string.IsNullOrEmpty(this.Product)) + if (!String.IsNullOrEmpty(this.Product)) { arguments.AppendFormat(" \"{0}\" ", this.Product); } @@ -310,6 +310,12 @@ namespace WixTestTools /// ERROR_CALL_NOT_IMPLEMENTED = 120, + /// + /// ERROR_FILENAME_EXCED_RANGE 206 + /// The filename or extension is too long. + /// + ERROR_FILENAME_EXCED_RANGE = 206, + /// /// ERROR_APPHELP_BLOCK 1259 /// If Windows Installer determines a product may be incompatible with the current operating system, diff --git a/src/test/burn/WixTestTools/TestTool.cs b/src/test/burn/WixTestTools/TestTool.cs index eb77c75b..79e7004c 100644 --- a/src/test/burn/WixTestTools/TestTool.cs +++ b/src/test/burn/WixTestTools/TestTool.cs @@ -229,7 +229,7 @@ namespace WixTestTools returnValue.AppendLine("Tool run result:"); returnValue.AppendLine("----------------"); returnValue.AppendLine("Command:"); - returnValue.AppendLine($"\"{result.StartInfo.FileName}\" {result.StartInfo.Arguments}"); + returnValue.AppendLine($"\"{result.FileName}\" {result.Arguments}"); returnValue.AppendLine(); returnValue.AppendLine("Standard Output:"); foreach (var line in result.StandardOutput ?? new string[0]) diff --git a/src/test/burn/WixToolsetTest.BurnE2E/LongPathTests.cs b/src/test/burn/WixToolsetTest.BurnE2E/LongPathTests.cs new file mode 100644 index 00000000..ba793d7a --- /dev/null +++ b/src/test/burn/WixToolsetTest.BurnE2E/LongPathTests.cs @@ -0,0 +1,298 @@ +// 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. + +namespace WixToolsetTest.BurnE2E +{ + using System; + using System.ComponentModel; + using System.IO; + using System.Runtime.InteropServices; + using Microsoft.Win32; + using WixBuildTools.TestSupport; + using WixTestTools; + using WixToolset.Mba.Core; + using Xunit; + using Xunit.Abstractions; + + public class LongPathTests : BurnE2ETests + { + public LongPathTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } + + [RuntimeFact] + public void CanInstallAndUninstallSimpleBundle_x86_wixstdba() + { + this.CanInstallAndUninstallSimpleBundle("PackageA", "BundleA"); + } + + [RuntimeFact] + public void CanInstallAndUninstallSimpleBundle_x86_testba() + { + this.CanInstallAndUninstallSimpleBundle("PackageA", "BundleB"); + } + + [RuntimeFact] + public void CanInstallAndUninstallSimpleBundle_x86_dnctestba() + { + this.CanInstallAndUninstallSimpleBundle("PackageA", "BundleC"); + } + + [RuntimeFact] + public void CanInstallAndUninstallSimpleBundle_x86_wixba() + { + this.CanInstallAndUninstallSimpleBundle("PackageA", "BundleD"); + } + + [RuntimeFact] + public void CanInstallAndUninstallSimpleBundle_x64_wixstdba() + { + this.CanInstallAndUninstallSimpleBundle("PackageA_x64", "BundleA_x64"); + } + + [RuntimeFact] + public void CanInstallAndUninstallSimplePerUserBundle_x64_wixstdba() + { + this.CanInstallAndUninstallSimpleBundle("PackageApu_x64", "BundleApu_x64", "PackagePerUser.wxs", unchecked((int)0xc0000005)); + } + + [RuntimeFact] + public void CanInstallAndUninstallSimpleBundle_x64_testba() + { + this.CanInstallAndUninstallSimpleBundle("PackageA_x64", "BundleB_x64"); + } + + [RuntimeFact] + public void CanInstallAndUninstallSimpleBundle_x64_dnctestba() + { + this.CanInstallAndUninstallSimpleBundle("PackageA_x64", "BundleC_x64"); + } + + [RuntimeFact] + public void CanInstallAndUninstallSimpleBundle_x64_dncwixba() + { + this.CanInstallAndUninstallSimpleBundle("PackageA_x64", "BundleD_x64"); + } + + private void CanInstallAndUninstallSimpleBundle(string packageName, string bundleName, string fileName = "Package.wxs", int? alternateExitCode = null) + { + var package = this.CreatePackageInstaller(Path.Combine("..", "BasicFunctionalityTests", packageName)); + + var bundle = this.CreateBundleInstaller(Path.Combine("..", "BasicFunctionalityTests", bundleName)); + bundle.AlternateExitCode = alternateExitCode; + + using var dfs = new DisposableFileSystem(); + var baseFolder = GetLongPath(dfs.GetFolder()); + + var packageSourceCodeInstalled = package.GetInstalledFilePath(fileName); + + // Source file should *not* be installed + Assert.False(File.Exists(packageSourceCodeInstalled), $"{packageName} payload should not be there on test start: {packageSourceCodeInstalled}"); + + var bundleFileInfo = new FileInfo(bundle.Bundle); + var bundleCopiedPath = Path.Combine(baseFolder, bundleFileInfo.Name); + bundleFileInfo.CopyTo(bundleCopiedPath); + + bundle.Install(bundleCopiedPath); + bundle.VerifyRegisteredAndInPackageCache(); + + // Source file should be installed + Assert.True(File.Exists(packageSourceCodeInstalled), $"Should have found {packageName} payload installed at: {packageSourceCodeInstalled}"); + + if (alternateExitCode == bundle.LastExitCode) + { + WixAssert.Skip($"Install exited with {bundle.LastExitCode}"); + } + + bundle.Uninstall(bundleCopiedPath); + + // Source file should *not* be installed + Assert.False(File.Exists(packageSourceCodeInstalled), $"{packageName} payload should have been removed by uninstall from: {packageSourceCodeInstalled}"); + + bundle.VerifyUnregisteredAndRemovedFromPackageCache(); + + if (alternateExitCode == bundle.LastExitCode) + { + WixAssert.Skip($"Uninstall exited with {bundle.LastExitCode}"); + } + } + + [RuntimeFact] + public void CanLayoutNonCompressedBundleToLongPath() + { + var nonCompressedBundle = this.CreateBundleInstaller("NonCompressedBundle"); + var testBAController = this.CreateTestBAController(); + + testBAController.SetPackageRequestedState("NetFx48Web", RequestState.None); + + using var dfs = new DisposableFileSystem(); + var layoutDirectory = GetLongPath(dfs.GetFolder()); + + nonCompressedBundle.Layout(layoutDirectory); + nonCompressedBundle.VerifyUnregisteredAndRemovedFromPackageCache(); + + Assert.True(File.Exists(Path.Combine(layoutDirectory, "NonCompressedBundle.exe"))); + Assert.True(File.Exists(Path.Combine(layoutDirectory, "PackageA.msi"))); + Assert.True(File.Exists(Path.Combine(layoutDirectory, "1a.cab"))); + Assert.False(File.Exists(Path.Combine(layoutDirectory, @"redist\ndp48-web.exe"))); + } + + [RuntimeFact] + public void CanInstallNonCompressedBundleWithLongTempPath() + { + this.InstallNonCompressedBundle(longTemp: true, useOriginalTempForLog: true); + } + + [RuntimeFact] + public void CannotInstallNonCompressedBundleWithLongPackageCachePath() + { + var installLogPath = this.InstallNonCompressedBundle((int)MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE, longPackageCache: true); + Assert.True(LogVerifier.MessageInLogFile(installLogPath, @"Error 0x80070643: Failed to install MSI package")); + } + + [RuntimeFact] + public void CannotInstallNonCompressedBundleWithLongWorkingPath() + { + var installLogPath = this.InstallNonCompressedBundle((int)MSIExec.MSIExecReturnCode.ERROR_FILENAME_EXCED_RANGE | unchecked((int)0x80070000), longWorkingPath: true); + Assert.True(LogVerifier.MessageInLogFile(installLogPath, @"Error 0x800700ce: Failed to load BA DLL")); + } + + public string InstallNonCompressedBundle(int expectedExitCode = 0, bool longTemp = false, bool useOriginalTempForLog = false, bool longWorkingPath = false, bool longPackageCache = false, int? alternateExitCode = null) + { + var deletePolicyKey = false; + string originalEngineWorkingDirectoryValue = null; + string originalPackageCacheValue = null; + var originalTemp = Environment.GetEnvironmentVariable("TMP"); + var packageA = this.CreatePackageInstaller("PackageA"); + var nonCompressedBundle = this.CreateBundleInstaller("NonCompressedBundle"); + var policyPath = nonCompressedBundle.GetFullBurnPolicyRegistryPath(); + string installLogPath = null; + + try + { + using var dfs = new DisposableFileSystem(); + var originalBaseFolder = dfs.GetFolder(); + var baseFolder = GetLongPath(originalBaseFolder); + var sourceFolder = Path.Combine(baseFolder, "source"); + var workingFolder = Path.Combine(baseFolder, "working"); + var tempFolder = Path.Combine(originalBaseFolder, new string('d', 260 - originalBaseFolder.Length - 2)); + var packageCacheFolder = Path.Combine(baseFolder, "package cache"); + + var copyResult = TestDataFolderFileSystem.RobocopyFolder(this.TestContext.TestDataFolder, sourceFolder); + Assert.True(copyResult.ExitCode >= 0 && copyResult.ExitCode < 8, $"Exit code: {copyResult.ExitCode}\r\nOutput: {String.Join("\r\n", copyResult.StandardOutput)}\r\nError: {String.Join("\r\n", copyResult.StandardError)}"); + + var bundleFileInfo = new FileInfo(nonCompressedBundle.Bundle); + var bundleCopiedPath = Path.Combine(sourceFolder, bundleFileInfo.Name); + + var policyKey = Registry.LocalMachine.OpenSubKey(policyPath, writable: true); + if (policyKey == null) + { + policyKey = Registry.LocalMachine.CreateSubKey(policyPath, writable: true); + deletePolicyKey = true; + } + + using (policyKey) + { + originalEngineWorkingDirectoryValue = policyKey.GetValue("EngineWorkingDirectory") as string; + originalPackageCacheValue = policyKey.GetValue("PackageCache") as string; + + if (longWorkingPath) + { + policyKey.SetValue("EngineWorkingDirectory", workingFolder); + } + + if (longPackageCache) + { + policyKey.SetValue("PackageCache", packageCacheFolder); + } + } + + if (longTemp) + { + Environment.SetEnvironmentVariable("TMP", tempFolder); + + if (useOriginalTempForLog) + { + nonCompressedBundle.LogDirectory = originalTemp; + } + } + + try + { + nonCompressedBundle.AlternateExitCode = alternateExitCode; + installLogPath = nonCompressedBundle.Install(bundleCopiedPath, expectedExitCode); + + if (alternateExitCode == nonCompressedBundle.LastExitCode) + { + WixAssert.Skip($"Install exited with {nonCompressedBundle.LastExitCode}"); + } + } + finally + { + TestDataFolderFileSystem.RobocopyFolder(tempFolder, originalTemp); + } + + installLogPath = Path.Combine(originalTemp, Path.GetFileName(installLogPath)); + + if (expectedExitCode == 0) + { + var registration = nonCompressedBundle.VerifyRegisteredAndInPackageCache(); + packageA.VerifyInstalled(true); + + nonCompressedBundle.Uninstall(registration.CachePath); + + if (alternateExitCode == nonCompressedBundle.LastExitCode) + { + WixAssert.Skip($"Uninstall exited with {nonCompressedBundle.LastExitCode}"); + } + } + + nonCompressedBundle.VerifyUnregisteredAndRemovedFromPackageCache(); + packageA.VerifyInstalled(false); + + return installLogPath; + } + finally + { + Environment.SetEnvironmentVariable("TMP", originalTemp); + + if (deletePolicyKey) + { + Registry.LocalMachine.DeleteSubKeyTree(policyPath); + } + else + { + using (var policyKey = Registry.LocalMachine.OpenSubKey(policyPath, writable: true)) + { + policyKey?.SetValue("EngineWorkingDirectory", originalEngineWorkingDirectoryValue); + policyKey?.SetValue("PackageCache", originalPackageCacheValue); + } + } + } + } + + private static string GetLongPath(string baseFolder) + { + Directory.CreateDirectory(baseFolder); + + // Try to create a directory that is longer than MAX_PATH but without the \\?\ prefix to detect OS support for long paths. + // Need to PInvoke CreateDirectoryW directly because .NET methods will append the \\?\ prefix. + foreach (var c in new char[] { 'a', 'b', 'c' }) + { + baseFolder = Path.Combine(baseFolder, new string(c, 100)); + if (!CreateDirectoryW(baseFolder, IntPtr.Zero)) + { + int lastError = Marshal.GetLastWin32Error(); + if (lastError == 206) + { + WixAssert.Skip($"MAX_PATH is being enforced ({baseFolder})"); + } + throw new Win32Exception(lastError); + } + } + + return baseFolder; + } + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private extern static bool CreateDirectoryW(string lpPathName, IntPtr lpSecurityAttributes); + } +} diff --git a/src/test/burn/WixToolsetTest.BurnE2E/testhost.longpathaware.manifest b/src/test/burn/WixToolsetTest.BurnE2E/testhost.longpathaware.manifest new file mode 100644 index 00000000..a56ab91a --- /dev/null +++ b/src/test/burn/WixToolsetTest.BurnE2E/testhost.longpathaware.manifest @@ -0,0 +1,11 @@ + + + + + + + + true + + + diff --git a/src/test/burn/test_burn.cmd b/src/test/burn/test_burn.cmd index 83401614..80467f39 100644 --- a/src/test/burn/test_burn.cmd +++ b/src/test/burn/test_burn.cmd @@ -9,11 +9,16 @@ @if /i "%1"=="test" set RuntimeTestsEnabled=true @if not "%1"=="" shift & goto parse_args +@set _B=%~dp0..\..\..\build\IntegrationBurn\%_C% + @echo Burn integration tests %_C% msbuild -t:Build -Restore -p:Configuration=%_C% -warnaserror -bl:%_L%\test_burn_build.binlog || exit /b msbuild -t:Build -Restore TestData\TestData.proj -p:Configuration=%_C% -m -bl:%_L%\test_burn_data_build.binlog || exit /b +"%_B%\net35\win-x86\testexe.exe" /dm "%_B%\netcoreapp3.1\testhost.exe" +mt.exe -manifest "WixToolsetTest.BurnE2E\testhost.longpathaware.manifest" -updateresource:"%_B%\netcoreapp3.1\testhost.exe" + @if not "%RuntimeTestsEnabled%"=="true" goto :LExit dotnet test -c %_C% --no-build WixToolsetTest.BurnE2E -l "trx;LogFileName=%_L%\TestResults\WixToolsetTest.BurnE2E.trx" || exit /b diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs index 672bb235..67a6cb4a 100644 --- a/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs @@ -231,6 +231,10 @@ namespace WixToolset.Core.Burn.Bundles writer.WriteEndElement(); } + writer.WriteStartElement("longPathAware", ws2016Namespace); + writer.WriteString("true"); + writer.WriteEndElement(); + writer.WriteEndElement(); // writer.WriteEndElement(); // } diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BundleFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BundleFixture.cs index bc7fe7a4..a281ad0f 100644 --- a/src/wix/test/WixToolsetTest.CoreIntegration/BundleFixture.cs +++ b/src/wix/test/WixToolsetTest.CoreIntegration/BundleFixture.cs @@ -169,7 +169,10 @@ namespace WixToolsetTest.CoreIntegration "" + "" + "" + - "true/pmPerMonitorV2, PerMonitor" + + "" + + "true/pmPerMonitorV2, PerMonitor" + + "true" + + "" + "", actualManifestData); } } @@ -215,7 +218,10 @@ namespace WixToolsetTest.CoreIntegration "" + "" + "" + - "true/pmPerMonitorV2, PerMonitor" + + "" + + "true/pmPerMonitorV2, PerMonitor" + + "true" + + "" + "", actualManifestData); var extractResult = BundleExtractor.ExtractAllContainers(null, exePath, baFolderPath, attachedFolderPath, extractFolderPath); -- cgit v1.2.3-55-g6feb