Skip to content

Commit

Permalink
add wil::create_file_create, create_file_open, etc. tame use of Creat…
Browse files Browse the repository at this point in the history
…eFileW (#382)

* add wil::create_file_open, _create

* get it building

* Raymond's feedback

* tweak test

* add wil::create_file_open, _create

* get it building

* Raymond's feedback

* tweak test

* copy over all of my tests

* skip some tests down level.

* now supports C++11

* cleanup link

* cleanup operators

* inheritHandle after attributes

* remove static_assert

* WIL_NO_FILE_TYPE_OPERATORS

* GetLastError after CreateFileW

* drop create_file_create_behavior comment

---------

Co-authored-by: Duncan Horn <40036384+dunhor@users.noreply.github.com>
  • Loading branch information
ChrisGuzak and dunhor authored Nov 8, 2023
1 parent 025ca12 commit ce501f7
Show file tree
Hide file tree
Showing 2 changed files with 326 additions and 7 deletions.
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
{
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());
return std::move(result.file);
}

/// @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,
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]")
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)

0 comments on commit ce501f7

Please sign in to comment.