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 4 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
225 changes: 222 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 @@ -1043,8 +1046,224 @@ namespace wil
THROW_IF_FAILED(GetFileInfoNoThrow<infoClass>(fileHandle, result));
return result;
}

// Helpers to make the CreateFileW API easier to use. This segregates the OPEN_EXISTING "open an existing file"
// cases from the "create a new file" that has 4 variations represented in the create_file_create_behavior enum.
ChrisGuzak marked this conversation as resolved.
Show resolved Hide resolved

// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew?devlangs=cpp&f1url=%3FappId%3DDev16IDEF1%26l%3DEN-US%26k%3Dk(FILEAPI%252FCreateFileW)%3Bk(CreateFileW)%3Bk(DevLang-C%252B%252B)%3Bk(TargetOS-Windows)%26rd%3Dtrue

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,
bool inheritHandle = false,
DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL) noexcept
ChrisGuzak marked this conversation as resolved.
Show resolved Hide resolved
{
SECURITY_ATTRIBUTES secAttributes{ sizeof(secAttributes) };
secAttributes.bInheritHandle = inheritHandle;
return { CreateFileW(path, dwDesiredAccess, dwShareMode, &secAttributes, OPEN_EXISTING, dwFlagsAndAttributes, nullptr),
ChrisGuzak marked this conversation as resolved.
Show resolved Hide resolved
::GetLastError() };
ChrisGuzak marked this conversation as resolved.
Show resolved Hide resolved
}

/** 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,
bool inheritHandle = false,
DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL)
{
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
{
return { CreateFileW(
path, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreateDisposition, dwFlagsAndAttributes, hTemplateFile),
::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 NO_FILE_TYPE_OPERATORS
ChrisGuzak marked this conversation as resolved.
Show resolved Hide resolved
// namespace scope
inline bool operator!=(const FILE_ID_128& left, const FILE_ID_128& right)
{
static_assert(sizeof(left) == sizeof(right), "size must match");
ChrisGuzak marked this conversation as resolved.
Show resolved Hide resolved
return memcmp(&left, &right, sizeof(left)) != 0;
ChrisGuzak marked this conversation as resolved.
Show resolved Hide resolved
}

inline bool operator==(const FILE_ID_128& left, const FILE_ID_128& right)
{
static_assert(sizeof(left) == sizeof(right), "size must match");
return memcmp(&left, &right, sizeof(left)) == 0;
}
#endif

#endif // __WIL_FILESYSTEM_INCLUDED
19 changes: 19 additions & 0 deletions tests/FileSystemTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -651,5 +651,24 @@ TEST_CASE("FileSystemTests::QueryFullProcessImageNameW", "[filesystem]")
REQUIRE_SUCCEEDED((wil::QueryFullProcessImageNameW<wil::unique_cotaskmem_string, 15>(::GetCurrentProcess(), 0, path)));
}

#if _HAS_CXX17
#ifdef WIL_ENABLE_EXCEPTIONS

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

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

auto [handle, error] = wil::try_open_file(path.c_str());
REQUIRE(handle.is_valid());
REQUIRE(error == ERROR_ALREADY_EXISTS);
}

#endif
#endif

#endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
Loading