@samitouri / QOSAMI-WSL / commits / 10a6ac3e

Fix E_ACCESSDENIED during download if temp is hidden (#40708)

The winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync function does not accept folders with hidden or system attributes. To solve that, this PR avoids the GetFolderFromPathAsync call and opens the file stream directly.

Feng Wang committed Jul 3, 2026 at 11:25 UTC 10a6ac3eee3bd8f109ae31de3388d85a953c4ecb
2 files changed +104 -8
src/windows/common/wslutil.cpp
+39 -8
@@ -380,14 +380,45 @@ std::wstring wsl::windows::common::wslutil::DownloadFileImpl(
380 Filename = Url.substr(lastSlash + 1);
381 }
382
383 - const auto downloadFolder =
384 - winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(std::filesystem::temp_directory_path().wstring()).get();
385 -
386 - const auto file =
387 - downloadFolder.CreateFileAsync(Filename, winrt::Windows::Storage::CreationCollisionOption::GenerateUniqueName).get();
388 - auto deleteFileOnFailure = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { file.DeleteAsync().get(); });
383 + // GetFolderFromPathAsync won't work if the folder is hidden or system.
384 + auto downloadFolderPath = std::filesystem::temp_directory_path();
385 + auto filenameStem = std::filesystem::path(Filename).stem().wstring();
386 + auto filenameExtension = std::filesystem::path(Filename).extension().wstring();
387 + std::wstring filePath{};
388 + winrt::Windows::Storage::Streams::IRandomAccessStream outputStream{};
389 + for (int suffix = 1; outputStream == nullptr; suffix++)
390 + {
391 + if (suffix == 1)
392 + {
393 + filePath = (downloadFolderPath / Filename).wstring();
394 + }
395 + else
396 + {
397 + filePath = (downloadFolderPath / std::format(L"{} ({}){}", filenameStem, suffix, filenameExtension)).wstring();
398 + }
399 + try
400 + {
401 + outputStream = winrt::Windows::Storage::Streams::FileRandomAccessStream::OpenAsync(
402 + filePath,
403 + winrt::Windows::Storage::FileAccessMode::ReadWrite,
404 + winrt::Windows::Storage::StorageOpenOptions::None,
405 + winrt::Windows::Storage::Streams::FileOpenDisposition::CreateNew)
406 + .get();
407 + }
408 + catch (...)
409 + {
410 + if (wil::ResultFromCaughtException() != HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS))
411 + {
412 + throw;
413 + }
414 + }
415 + }
416
390 - const auto outputStream = file.OpenAsync(winrt::Windows::Storage::FileAccessMode::ReadWrite).get().GetOutputStreamAt(0);
417 + auto deleteFileOnFailure = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
418 + outputStream.Close();
419 + std::error_code ec;
420 + std::filesystem::remove(filePath, ec);
421 + });
422
423 // By default downloaded files are cached in %appdata%/local/packages/{package-family}/AC/InetCache .
424 // Disable caching since there's no reason to keep local copies of .msixbundle files.
@@ -421,7 +452,7 @@ std::wstring wsl::windows::common::wslutil::DownloadFileImpl(
452 download.get();
453 deleteFileOnFailure.release();
454
424 - return file.Path().c_str();
455 + return filePath;
456 }
457
458 [[nodiscard]] HANDLE wsl::windows::common::wslutil::DuplicateHandle(_In_ HANDLE Handle, _In_ std::optional<DWORD> DesiredAccess, _In_ BOOL InheritHandle)
test/windows/UnitTests.cpp
+65
@@ -7448,5 +7448,70 @@ Error code: Wsl/InstallDistro/WSL_E_INVALID_JSON\r\n",
7448 }
7449 }
7450
7451 + TEST_METHOD(DownloadToHiddenSystemTempFolder)
7452 + {
7453 + // Avoid contaminating the real temp folder.
7454 + const auto testTempFolder = std::filesystem::temp_directory_path() / L"wsl-download-test";
7455 + std::filesystem::create_directories(testTempFolder);
7456 + auto cleanupTempFolder = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
7457 + std::error_code error;
7458 + std::filesystem::remove_all(testTempFolder, error);
7459 + });
7460 +
7461 + const auto originalAttributes = GetFileAttributesW(testTempFolder.c_str());
7462 + VERIFY_IS_TRUE(originalAttributes != INVALID_FILE_ATTRIBUTES);
7463 + VERIFY_IS_TRUE(SetFileAttributesW(testTempFolder.c_str(), originalAttributes | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM));
7464 +
7465 + ScopedEnvVariable temp(L"TEMP", testTempFolder.wstring());
7466 + ScopedEnvVariable tmp(L"TMP", testTempFolder.wstring());
7467 +
7468 + VERIFY_IS_TRUE(std::filesystem::equivalent(std::filesystem::temp_directory_path(), testTempFolder));
7469 +
7470 + constexpr USHORT port = 6666;
7471 + const auto endpoint = std::format(L"http://127.0.0.1:{}/", port);
7472 + constexpr auto fileName = L"downloaded-file.bin";
7473 + constexpr auto fileContent = L"wsl download test content";
7474 + UniqueWebServer server(endpoint.c_str(), fileContent);
7475 +
7476 + const auto url = endpoint + fileName;
7477 + const auto noProgress = [](uint64_t, uint64_t) {};
7478 +
7479 + wsl::shared::retry::RetryWithTimeout<void>(
7480 + [&]() {
7481 + wil::unique_socket probe{socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)};
7482 + THROW_LAST_ERROR_IF(!probe);
7483 +
7484 + sockaddr_in address{};
7485 + address.sin_family = AF_INET;
7486 + address.sin_port = htons(port);
7487 + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
7488 +
7489 + THROW_LAST_ERROR_IF(connect(probe.get(), reinterpret_cast<const sockaddr*>(&address), sizeof(address)) == SOCKET_ERROR);
7490 + },
7491 + std::chrono::milliseconds(500),
7492 + std::chrono::seconds(5));
7493 +
7494 + const auto firstPath = wsl::windows::common::wslutil::DownloadFileImpl(url, L"", noProgress);
7495 +
7496 + auto readFile = [](const std::filesystem::path& Path) {
7497 + std::ifstream file(Path, std::ios::binary);
7498 + VERIFY_IS_TRUE(file.good());
7499 + return std::string{std::istreambuf_iterator<char>(file), {}};
7500 + };
7501 +
7502 + VERIFY_ARE_EQUAL(std::filesystem::path(firstPath).parent_path(), testTempFolder);
7503 + VERIFY_ARE_EQUAL(std::filesystem::path(firstPath).filename().wstring(), std::wstring(fileName));
7504 + VERIFY_IS_TRUE(std::filesystem::exists(firstPath));
7505 + VERIFY_ARE_EQUAL(readFile(firstPath), wsl::shared::string::WideToMultiByte(fileContent));
7506 +
7507 + const auto secondPath = wsl::windows::common::wslutil::DownloadFileImpl(url, L"", noProgress);
7508 +
7509 + VERIFY_ARE_EQUAL(std::filesystem::path(secondPath).parent_path(), testTempFolder);
7510 + VERIFY_ARE_EQUAL(std::filesystem::path(secondPath).filename().wstring(), std::wstring(L"downloaded-file (2).bin"));
7511 + VERIFY_IS_TRUE(std::filesystem::exists(firstPath));
7512 + VERIFY_IS_TRUE(std::filesystem::exists(secondPath));
7513 + VERIFY_ARE_EQUAL(readFile(secondPath), wsl::shared::string::WideToMultiByte(fileContent));
7514 + }
7515 +
7516 }; // namespace UnitTests
7517 } // namespace UnitTests