Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add wil::create_file_create, create_file_open, etc. tame use of CreateFileW #382

Merged
merged 20 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 215 additions & 3 deletions include/wil/filesystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <combaseapi.h> // Needed for CoTaskMemFree() used in output of some helpers.
#include <winbase.h> // LocalAlloc
#include <PathCch.h>
#include "wistd_type_traits.h"
#include "result.h"
#include "win32_helpers.h"
#include "resource.h"
Expand Down Expand Up @@ -197,6 +198,7 @@ namespace wil
};
DEFINE_ENUM_FLAG_OPERATORS(RemoveDirectoryOptions);

/// @cond
namespace details
{
// Reparse points should not be traversed in most recursive walks of the file system,
Expand All @@ -208,6 +210,7 @@ namespace wil
(IsReparseTagDirectory(info.ReparseTag) || (info.ReparseTag == IO_REPARSE_TAG_WCI))));
}
}
/// @endcond

// Retrieve a handle to a directory only if it is safe to recurse into.
inline wil::unique_hfile TryCreateFileCanRecurseIntoDirectory(PCWSTR path, PWIN32_FIND_DATAW fileFindData, DWORD access = GENERIC_READ | /*DELETE*/ 0x00010000L, DWORD share = FILE_SHARE_READ)
Expand Down Expand Up @@ -716,10 +719,10 @@ namespace wil
{
for (auto const& info : create_next_entry_offset_iterator(reinterpret_cast<FILE_NOTIFY_INFORMATION *>(readerState->m_readBuffer)))
{
wchar_t realtiveFileName[MAX_PATH];
StringCchCopyNW(realtiveFileName, ARRAYSIZE(realtiveFileName), info.FileName, info.FileNameLength / sizeof(info.FileName[0]));
wchar_t relativeFileName[MAX_PATH];
StringCchCopyNW(relativeFileName, ARRAYSIZE(relativeFileName), info.FileName, info.FileNameLength / sizeof(info.FileName[0]));

readerState->m_callback(static_cast<FolderChangeEvent>(info.Action), realtiveFileName);
readerState->m_callback(static_cast<FolderChangeEvent>(info.Action), relativeFileName);
}
}
else if (result == ERROR_NOTIFY_ENUM_DIR)
Expand Down Expand Up @@ -1047,8 +1050,217 @@ namespace wil
THROW_IF_FAILED(GetFileInfoNoThrow<infoClass>(fileHandle, result));
return result;
}

// Helpers to make the CreateFileW API easier to use.
// https://learn.microsoft.com/windows/win32/api/fileapi/nf-fileapi-createfilew

struct file_and_error_result
ChrisGuzak marked this conversation as resolved.
Show resolved Hide resolved
{
file_and_error_result(HANDLE file_handle, DWORD error) : file(file_handle), last_error(error)
{
}

wil::unique_hfile file;
DWORD last_error{};
};

/** Non-throwing open existing using OPEN_EXISTING.
~~~
auto handle = wil::try_open_file(filePath.c_str());
~~~
*/
inline file_and_error_result try_open_file(PCWSTR path, DWORD dwDesiredAccess = FILE_READ_ACCESS,
DWORD dwShareMode = FILE_SHARE_READ, DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL,
bool inheritHandle = false) noexcept
{
SECURITY_ATTRIBUTES secAttributes{ sizeof(secAttributes) };
secAttributes.bInheritHandle = inheritHandle;
auto handle = CreateFileW(path, dwDesiredAccess, dwShareMode, &secAttributes, OPEN_EXISTING, dwFlagsAndAttributes, nullptr);
return { handle, ::GetLastError() };
}

/** open existing using OPEN_EXISTING, throws on error.
~~~
auto handle = wil::open_file(filePath.c_str());
~~~
*/
inline wil::unique_hfile open_file(PCWSTR path, DWORD dwDesiredAccess = FILE_READ_ACCESS,
DWORD dwShareMode = FILE_SHARE_READ, DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL,
bool inheritHandle = false) noexcept
{
auto result = try_open_file(path, dwDesiredAccess, dwShareMode, inheritHandle, dwFlagsAndAttributes);
THROW_WIN32_IF(result.last_error, !result.file.is_valid());
ChrisGuzak marked this conversation as resolved.
Show resolved Hide resolved
return std::move(result.file);
ChrisGuzak marked this conversation as resolved.
Show resolved Hide resolved
}

/// @cond
namespace details
{
template<DWORD dwCreateDisposition>
file_and_error_result create_file(PCWSTR path, DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile) noexcept
{
auto handle = CreateFileW(
path, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreateDisposition, dwFlagsAndAttributes, hTemplateFile);
return { handle, ::GetLastError() };
}
}
/// @endcond


/** create using CREATE_NEW, returns handle and error code.
~~~
auto [handle, error = wil::try_create_new_file(filePath.c_str());
~~~
*/
inline file_and_error_result try_create_new_file(PCWSTR path,
DWORD dwDesiredAccess = FILE_READ_ACCESS | FILE_WRITE_ACCESS,
DWORD dwShareMode = FILE_SHARE_READ,
LPSECURITY_ATTRIBUTES lpSecurityAttributes = nullptr,
DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL,
ChrisGuzak marked this conversation as resolved.
Show resolved Hide resolved
HANDLE hTemplateFile = nullptr) noexcept
{
return details::create_file<CREATE_NEW>(
path, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwFlagsAndAttributes, hTemplateFile);
}

/** create using OPEN_ALWAYS, returns handle and error code.
~~~
auto [handle, error = wil::try_open_or_create_file(filePath.c_str());
~~~
*/
inline file_and_error_result try_open_or_create_file(PCWSTR path,
DWORD dwDesiredAccess = FILE_READ_ACCESS | FILE_WRITE_ACCESS,
DWORD dwShareMode = FILE_SHARE_READ,
LPSECURITY_ATTRIBUTES lpSecurityAttributes = nullptr,
DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL,
HANDLE hTemplateFile = nullptr) noexcept
{
return details::create_file<OPEN_ALWAYS>(
path, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwFlagsAndAttributes, hTemplateFile);
}

/** create using CREATE_ALWAYS, returns handle and error code.
~~~
auto [handle, error = wil::try_open_or_truncate_existing_file(filePath.c_str());
~~~
*/
inline file_and_error_result try_open_or_truncate_existing_file(PCWSTR path,
DWORD dwDesiredAccess = FILE_READ_ACCESS | FILE_WRITE_ACCESS,
DWORD dwShareMode = FILE_SHARE_READ,
LPSECURITY_ATTRIBUTES lpSecurityAttributes = nullptr,
DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL,
HANDLE hTemplateFile = nullptr) noexcept
{
return details::create_file<CREATE_ALWAYS>(
path, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwFlagsAndAttributes, hTemplateFile);
}

/** create using TRUNCATE_EXISTING, returns handle and error code.
~~~
auto [handle, error = wil::try_truncate_existing_file(filePath.c_str());
~~~
*/
inline file_and_error_result try_truncate_existing_file(PCWSTR path,
DWORD dwDesiredAccess = FILE_READ_ACCESS | FILE_WRITE_ACCESS | GENERIC_WRITE,
DWORD dwShareMode = FILE_SHARE_READ,
LPSECURITY_ATTRIBUTES lpSecurityAttributes = nullptr,
DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL,
HANDLE hTemplateFile = nullptr) noexcept
{
return details::create_file<TRUNCATE_EXISTING>(
path, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwFlagsAndAttributes, hTemplateFile);
}

/** create using CREATE_NEW, returns the file handle, throws on error.
~~~
auto handle = wil::create_new_file(filePath.c_str());
~~~
*/
inline wil::unique_hfile create_new_file(PCWSTR path,
DWORD dwDesiredAccess = FILE_READ_ACCESS | FILE_WRITE_ACCESS,
DWORD dwShareMode = FILE_SHARE_READ,
LPSECURITY_ATTRIBUTES lpSecurityAttributes = nullptr,
DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL,
HANDLE hTemplateFile = nullptr) noexcept
{
auto result = try_create_new_file(
path, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwFlagsAndAttributes, hTemplateFile);
THROW_WIN32_IF(result.last_error, !result.file.is_valid());
return std::move(result.file);
}

/** create using OPEN_ALWAYS, returns the file handle, throws on error.
~~~
auto handle = wil::open_or_create_file(filePath.c_str());
~~~
*/
inline wil::unique_hfile open_or_create_file(PCWSTR path,
DWORD dwDesiredAccess = FILE_READ_ACCESS | FILE_WRITE_ACCESS,
DWORD dwShareMode = FILE_SHARE_READ,
LPSECURITY_ATTRIBUTES lpSecurityAttributes = nullptr,
DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL,
HANDLE hTemplateFile = nullptr) noexcept
{
auto result = try_open_or_create_file(
path, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwFlagsAndAttributes, hTemplateFile);
THROW_WIN32_IF(result.last_error, !result.file.is_valid());
return std::move(result.file);
}

/** create using CREATE_ALWAYS, returns the file handle, throws on error.
~~~
auto handle = wil::open_or_truncate_existing_file(filePath.c_str());
~~~
*/
inline wil::unique_hfile open_or_truncate_existing_file(PCWSTR path,
DWORD dwDesiredAccess = FILE_READ_ACCESS | FILE_WRITE_ACCESS,
DWORD dwShareMode = FILE_SHARE_READ,
LPSECURITY_ATTRIBUTES lpSecurityAttributes = nullptr,
DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL,
HANDLE hTemplateFile = nullptr) noexcept
{
auto result = try_open_or_truncate_existing_file(
path, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwFlagsAndAttributes, hTemplateFile);
THROW_WIN32_IF(result.last_error, !result.file.is_valid());
return std::move(result.file);
}

/** create using TRUNCATE_EXISTING, returns the file handle, throws on error.
~~~
auto handle = wil::truncate_existing_file(filePath.c_str());
~~~
*/
inline wil::unique_hfile truncate_existing_file(PCWSTR path,
DWORD dwDesiredAccess = FILE_READ_ACCESS | FILE_WRITE_ACCESS,
DWORD dwShareMode = FILE_SHARE_READ,
LPSECURITY_ATTRIBUTES lpSecurityAttributes = nullptr,
DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL,
HANDLE hTemplateFile = nullptr) noexcept
{
auto result = try_truncate_existing_file(
path, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwFlagsAndAttributes, hTemplateFile);
THROW_WIN32_IF(result.last_error, !result.file.is_valid());
return std::move(result.file);
}

#endif // _CPPUNWIND
#endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && (_WIN32_WINNT >= _WIN32_WINNT_WIN7)
}

#ifndef WIL_NO_FILE_TYPE_OPERATORS
inline bool operator==(const FILE_ID_128& left, const FILE_ID_128& right)
{
return memcmp(&left, &right, sizeof(left)) == 0;
}

inline bool operator!=(const FILE_ID_128& left, const FILE_ID_128& right)
{
return !operator==(left, right);
}
#endif

#endif // __WIL_FILESYSTEM_INCLUDED
115 changes: 111 additions & 4 deletions tests/FileSystemTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -628,18 +628,17 @@ TEST_CASE("FileSystemTests::VerifyGetModuleFileNameExW", "[filesystem]")
#endif
}

#ifdef WIL_ENABLE_EXCEPTIONS

TEST_CASE("FileSystemTests::QueryFullProcessImageNameW and GetModuleFileNameW", "[filesystem]")
{
#ifdef WIL_ENABLE_EXCEPTIONS
auto procName = wil::QueryFullProcessImageNameW<std::wstring>();
auto moduleName = wil::GetModuleFileNameW<std::wstring>();
REQUIRE(CompareStringOrdinal(procName.c_str(), -1, moduleName.c_str(), -1, TRUE) == CSTR_EQUAL);
#endif
}

TEST_CASE("FileSystemTests::GetFileInfo<FileStreamInfo>", "[filesystem]")
{
#ifdef WIL_ENABLE_EXCEPTIONS
auto path = wil::ExpandEnvironmentStringsW<std::wstring>(L"%TEMP%");
wil::unique_hfile handle(CreateFileW(path.c_str(), FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
Expand All @@ -650,7 +649,6 @@ TEST_CASE("FileSystemTests::GetFileInfo<FileStreamInfo>", "[filesystem]")
wistd::unique_ptr<FILE_STREAM_INFO> streamInfo;
auto hr = wil::GetFileInfoNoThrow<FileStreamInfo>(handle.get(), streamInfo);
REQUIRE(hr == S_OK);
#endif
}

TEST_CASE("FileSystemTests::QueryFullProcessImageNameW", "[filesystem]")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Not critical, but this could be pulled out of the ifdef for exception support

Expand All @@ -667,5 +665,114 @@ TEST_CASE("FileSystemTests::QueryFullProcessImageNameW", "[filesystem]")
REQUIRE_SUCCEEDED((wil::QueryFullProcessImageNameW<wil::unique_cotaskmem_string, 15>(::GetCurrentProcess(), 0, path)));
}

TEST_CASE("FileSystemTests::CreateFileW helpers", "[filesystem]")
{
// OPEN_EXISTING
{
auto path = wil::ExpandEnvironmentStringsW<std::wstring>(LR"(%TEMP%\open_existing_test)");

// arrange
{
auto handle = wil::open_or_create_file(path.c_str());
}

auto result = wil::try_open_file(path.c_str());
REQUIRE(result.file.is_valid());
REQUIRE(result.last_error == ERROR_SUCCESS);
}

// CREATE_ALWAYS
{
#if (NTDDI_VERSION >= NTDDI_WIN8)
FILE_ID_128 originalFileId{};

// arrange
auto overWriteTarget = wil::ExpandEnvironmentStringsW<std::wstring>(LR"(%temp%\create_always_test)");
DeleteFileW(overWriteTarget.c_str());

{
auto result = wil::try_create_new_file(overWriteTarget.c_str());
REQUIRE(result.file.is_valid());
REQUIRE(result.last_error == ERROR_SUCCESS); // file did not exist
originalFileId = wil::GetFileInfo<FileIdInfo>(result.file.get()).FileId;
}

auto result = wil::try_open_or_create_file(overWriteTarget.c_str());
REQUIRE(result.file.is_valid());
REQUIRE(result.last_error == ERROR_ALREADY_EXISTS); // file existed

auto newFileId = wil::GetFileInfo<FileIdInfo>(result.file.get()).FileId;
REQUIRE(originalFileId == newFileId); // Identity is the same
#endif
}

// CREATE_NEW
{
auto overWriteTarget = wil::ExpandEnvironmentStringsW<std::wstring>(LR"(%temp%\create_new_test)");
DeleteFileW(overWriteTarget.c_str());

{
auto result = wil::try_create_new_file(overWriteTarget.c_str());
REQUIRE(result.file.is_valid());
REQUIRE(result.last_error == ERROR_SUCCESS); // file did not exist
}

// note, file exists now
{
auto result = wil::try_create_new_file(overWriteTarget.c_str());
REQUIRE(!result.file.is_valid());
REQUIRE(result.last_error == ERROR_FILE_EXISTS); // file existed
}

}

// OPEN_ALWAYS
{
auto overWriteTarget = wil::ExpandEnvironmentStringsW<std::wstring>(LR"(%temp%\open_always_test)");

// arrange
{
DeleteFileW(overWriteTarget.c_str());
}

{
// act (does not exist case)
auto result = wil::try_open_or_create_file(overWriteTarget.c_str());

REQUIRE(result.file.is_valid());
REQUIRE(result.last_error == ERROR_SUCCESS);
}

// act again does exist case
auto result = wil::try_open_or_create_file(overWriteTarget.c_str());
REQUIRE(result.file.is_valid());
REQUIRE(result.last_error == ERROR_ALREADY_EXISTS);
}

// TRUNCATE_EXISTING
{
auto overWriteTarget = wil::ExpandEnvironmentStringsW<std::wstring>(LR"(%temp%\truncate_existing_test)");

// arrange
{
auto result = wil::try_open_or_create_file(overWriteTarget.c_str());
THROW_WIN32_IF(result.last_error, !result.file.is_valid());
const auto data = L"abcd";
DWORD written{};
THROW_IF_WIN32_BOOL_FALSE(WriteFile(result.file.get(), data, sizeof(data), &written, nullptr));
auto originalEndOfFile = wil::GetFileInfo<FileStandardInfo>(result.file.get()).EndOfFile;
THROW_HR_IF(E_UNEXPECTED, originalEndOfFile.QuadPart == 0);
}

// act
auto result = wil::try_truncate_existing_file(overWriteTarget.c_str());
THROW_WIN32_IF(result.last_error, !result.file.is_valid());
auto overWrittenEndOfFile = wil::GetFileInfo<FileStandardInfo>(result.file.get()).EndOfFile;
REQUIRE(overWrittenEndOfFile.QuadPart == 0);
}
}

#endif


#endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)