Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/ned14/llfio.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNiall Douglas (s [underscore] sourceforge {at} nedprod [dot] com) <spamtrap@nedprod.com>2022-01-31 13:50:54 +0300
committerNiall Douglas (s [underscore] sourceforge {at} nedprod [dot] com) <spamtrap@nedprod.com>2022-02-03 18:22:52 +0300
commit40cf8246f3d4f827c43f8e0bd03dd449eb17975f (patch)
tree14ac73fd04b74115c67ce2c8d383a69f690e235f
parent96ed2c670ee60657788595ff82da83c674f82774 (diff)
-rw-r--r--cmake/headers.cmake1
-rw-r--r--cmake/tests.cmake1
-rw-r--r--include/llfio/revision.hpp6
-rw-r--r--include/llfio/v2.0/detail/impl/windows/fs_handle.ipp456
-rw-r--r--include/llfio/v2.0/detail/impl/windows/import.hpp54
-rw-r--r--include/llfio/v2.0/fs_handle.hpp101
-rw-r--r--include/llfio/v2.0/path_view.hpp4
-rw-r--r--test/tests/extended_attributes.cpp84
8 files changed, 690 insertions, 17 deletions
diff --git a/cmake/headers.cmake b/cmake/headers.cmake
index 446573a9..f9418dbb 100644
--- a/cmake/headers.cmake
+++ b/cmake/headers.cmake
@@ -70,6 +70,7 @@ set(llfio_HEADERS
"include/llfio/v2.0/detail/impl/windows/byte_io_handle.ipp"
"include/llfio/v2.0/detail/impl/windows/byte_io_multiplexer.ipp"
"include/llfio/v2.0/detail/impl/windows/byte_socket_handle.ipp"
+ "include/llfio/v2.0/detail/impl/windows/byte_socket_sources/schannel.ipp"
"include/llfio/v2.0/detail/impl/windows/directory_handle.ipp"
"include/llfio/v2.0/detail/impl/windows/file_handle.ipp"
"include/llfio/v2.0/detail/impl/windows/fs_handle.ipp"
diff --git a/cmake/tests.cmake b/cmake/tests.cmake
index 8e64425f..9477a286 100644
--- a/cmake/tests.cmake
+++ b/cmake/tests.cmake
@@ -9,6 +9,7 @@ set(llfio_TESTS
"test/tests/directory_handle_enumerate/kernel_directory_handle_enumerate.cpp.hpp"
"test/tests/directory_handle_enumerate/runner.cpp"
"test/tests/dynamic_thread_pool_group.cpp"
+ "test/tests/extended_attributes.cpp"
"test/tests/fast_random_file_handle.cpp"
"test/tests/file_handle_create_close/kernel_file_handle.cpp.hpp"
"test/tests/file_handle_create_close/runner.cpp"
diff --git a/include/llfio/revision.hpp b/include/llfio/revision.hpp
index 8aaec64b..c5957f5d 100644
--- a/include/llfio/revision.hpp
+++ b/include/llfio/revision.hpp
@@ -1,4 +1,4 @@
// Note the second line of this file must ALWAYS be the git SHA, third line ALWAYS the git SHA update time
-#define LLFIO_PREVIOUS_COMMIT_REF f2c668ab5e45dc9ba57f82cea0316fc9d94b5af5
-#define LLFIO_PREVIOUS_COMMIT_DATE "2022-01-18 17:06:11 +00:00"
-#define LLFIO_PREVIOUS_COMMIT_UNIQUE f2c668ab
+#define LLFIO_PREVIOUS_COMMIT_REF ce0ed3e23748f8406cd44d8f37e7b693dd9f396d
+#define LLFIO_PREVIOUS_COMMIT_DATE "2022-01-31 10:50:59 +00:00"
+#define LLFIO_PREVIOUS_COMMIT_UNIQUE ce0ed3e2
diff --git a/include/llfio/v2.0/detail/impl/windows/fs_handle.ipp b/include/llfio/v2.0/detail/impl/windows/fs_handle.ipp
index 08628146..c843f619 100644
--- a/include/llfio/v2.0/detail/impl/windows/fs_handle.ipp
+++ b/include/llfio/v2.0/detail/impl/windows/fs_handle.ipp
@@ -1,5 +1,5 @@
/* A filing system handle
-(C) 2017-2020 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
+(C) 2017-2022 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
File Created: Aug 2017
@@ -42,8 +42,8 @@ result<path_handle> fs_handle::parent_path_handle(deadline d) const noexcept
{
windows_nt_kernel::init();
using namespace windows_nt_kernel;
- LLFIO_LOG_FUNCTION_CALL(this);
auto &h = _get_handle();
+ LLFIO_LOG_FUNCTION_CALL(&h);
LLFIO_WIN_DEADLINE_TO_SLEEP_INIT(d);
if(_devid == 0 && _inode == 0)
{
@@ -133,8 +133,8 @@ result<void> fs_handle::relink(const path_handle &base, path_view_type path, boo
{
windows_nt_kernel::init();
using namespace windows_nt_kernel;
- LLFIO_LOG_FUNCTION_CALL(this);
auto &h = _get_handle();
+ LLFIO_LOG_FUNCTION_CALL(&h);
// If the target is a win32 path, we need to convert to NT path and call ourselves
if(!base.is_valid() && !path.is_ntpath())
@@ -145,7 +145,9 @@ result<void> fs_handle::relink(const path_handle &base, path_view_type path, boo
{
return win32_error(ERROR_FILE_NOT_FOUND);
}
- auto unntpath = make_scope_exit([&NtPath]() noexcept {
+ auto unntpath = make_scope_exit(
+ [&NtPath]() noexcept
+ {
if(HeapFree(GetProcessHeap(), 0, NtPath.Buffer) == 0)
{
abort();
@@ -221,8 +223,8 @@ result<void> fs_handle::link(const path_handle &base, path_view_type path, deadl
{
windows_nt_kernel::init();
using namespace windows_nt_kernel;
- LLFIO_LOG_FUNCTION_CALL(this);
auto &h = _get_handle();
+ LLFIO_LOG_FUNCTION_CALL(&h);
// If the target is a win32 path, we need to convert to NT path and call ourselves
if(!base.is_valid() && !path.is_ntpath())
@@ -233,7 +235,9 @@ result<void> fs_handle::link(const path_handle &base, path_view_type path, deadl
{
return win32_error(ERROR_FILE_NOT_FOUND);
}
- auto unntpath = make_scope_exit([&NtPath]() noexcept {
+ auto unntpath = make_scope_exit(
+ [&NtPath]() noexcept
+ {
if(HeapFree(GetProcessHeap(), 0, NtPath.Buffer) == 0)
{
abort();
@@ -279,8 +283,8 @@ result<void> fs_handle::unlink(deadline d) noexcept
using flag = handle::flag;
windows_nt_kernel::init();
using namespace windows_nt_kernel;
- LLFIO_LOG_FUNCTION_CALL(this);
auto &h = _get_handle();
+ LLFIO_LOG_FUNCTION_CALL(&h);
HANDLE duph = INVALID_HANDLE_VALUE;
// Get myself DELETE privs if possible
if(h.is_directory())
@@ -391,6 +395,444 @@ result<void> fs_handle::unlink(deadline d) noexcept
return success();
}
+namespace detail
+{
+ static inline result<handle> create_alternate_stream(const handle &base, path_view path, handle::mode _mode, handle::creation _creation) noexcept
+ {
+ using namespace windows_nt_kernel;
+ const handle::caching _caching = handle::caching::all;
+ const handle::flag flags = handle::flag::none;
+ native_handle_type nativeh;
+ DWORD fileshare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
+ OUTCOME_TRY(auto &&access, access_mask_from_handle_mode(nativeh, _mode, flags));
+ OUTCOME_TRY(auto &&attribs, attributes_from_handle_caching_and_flags(nativeh, _caching, flags));
+ DWORD creatdisp = 0x00000001 /*FILE_OPEN*/;
+ switch(_creation)
+ {
+ case handle::creation::open_existing:
+ break;
+ case handle::creation::only_if_not_exist:
+ creatdisp = 0x00000002 /*FILE_CREATE*/;
+ break;
+ case handle::creation::if_needed:
+ creatdisp = 0x00000003 /*FILE_OPEN_IF*/;
+ break;
+ case handle::creation::truncate_existing:
+ creatdisp = 0x00000004 /*FILE_OVERWRITE*/;
+ break;
+ case handle::creation::always_new:
+ creatdisp = 0x00000000 /*FILE_SUPERSEDE*/;
+ break;
+ }
+
+ attribs &= 0x00ffffff; // the real attributes only, not the win32 flags
+ OUTCOME_TRY(auto &&ntflags, ntflags_from_handle_caching_and_flags(nativeh, _caching, flags));
+ ntflags |= 0x040 /*FILE_NON_DIRECTORY_FILE*/; // do not open a directory
+ IO_STATUS_BLOCK isb = make_iostatus();
+
+ // We need to prepend a ':', so this gets handled a little different
+ path_view::zero_terminated_rendered_path<> zpath(
+ [&]() -> path_view::zero_terminated_rendered_path<>
+ {
+ return visit(path,
+ [&](auto sv) -> path_view::zero_terminated_rendered_path<>
+ {
+ using sv_type = std::decay_t<decltype(sv)>;
+ using value_type = typename sv_type::value_type;
+ auto *buffer = (value_type *) alloca((sv.size() + 1) * sizeof(value_type));
+ buffer[0] = ':';
+ memcpy(buffer + 1, sv.data(), sv.size() * sizeof(value_type));
+ // Force an immediate render into the internal buffer
+ return path_view::zero_terminated_rendered_path<>(path_view(buffer, sv.size() + 1, path_view::not_zero_terminated));
+ });
+ }());
+ UNICODE_STRING _path{};
+ _path.Buffer = const_cast<wchar_t *>(zpath.data());
+ _path.MaximumLength = (_path.Length = static_cast<USHORT>(zpath.size() * sizeof(wchar_t))) + sizeof(wchar_t);
+
+ OBJECT_ATTRIBUTES oa{};
+ memset(&oa, 0, sizeof(oa));
+ oa.Length = sizeof(OBJECT_ATTRIBUTES);
+ oa.ObjectName = &_path;
+ oa.RootDirectory = base.native_handle().h;
+ oa.Attributes = 0; // 0x40 /*OBJ_CASE_INSENSITIVE*/;
+ // if(!!(flags & file_flags::int_opening_link))
+ // oa.Attributes|=0x100/*OBJ_OPENLINK*/;
+
+ LARGE_INTEGER AllocationSize{};
+ memset(&AllocationSize, 0, sizeof(AllocationSize));
+ NTSTATUS ntstat = NtCreateFile(&nativeh.h, access, &oa, &isb, &AllocationSize, attribs, fileshare, creatdisp, ntflags, nullptr, 0);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(nativeh.h, isb, deadline());
+ }
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ return handle(nativeh, _caching, flags);
+ }
+} // namespace detail
+
+result<span<path_view_component>> fs_handle::list_extended_attributes(span<byte> tofill) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ auto &h = _get_handle();
+ LLFIO_LOG_FUNCTION_CALL(&h);
+ IO_STATUS_BLOCK isb = make_iostatus();
+ NTSTATUS ntstat = NtQueryInformationFile(h.native_handle().h, &isb, tofill.data(), (ULONG) tofill.size(), FileStreamInformation);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(h.native_handle().h, isb, deadline());
+ }
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ span<path_view_component> filled;
+ {
+ auto *p = tofill.data();
+ bool done = false;
+ size_t count = 0;
+ do
+ {
+ auto *i = (_FILE_STREAM_INFORMATION *) p;
+ if(i->StreamNameLength > 1 && i->StreamName[0] == ':' && i->StreamName[1] != ':')
+ {
+ count++;
+ }
+ if(i->NextEntryOffset == 0)
+ {
+ done = true;
+ }
+ else
+ {
+ p += i->NextEntryOffset;
+ }
+ } while(!done && p < tofill.data() + isb.Information);
+ if(count == 0)
+ {
+ return filled;
+ }
+ const auto offset = (isb.Information + 7) & ~7;
+ auto tofillremaining = tofill.size() - offset;
+ if(count * sizeof(path_view_component) > tofillremaining)
+ {
+ return errc::no_buffer_space;
+ }
+ filled = {(path_view_component *) (p + offset), count};
+ }
+ {
+ auto *p = tofill.data();
+ bool done = false;
+ size_t count = 0;
+ do
+ {
+ auto *i = (_FILE_STREAM_INFORMATION *) p;
+ // Strip out all :: prefixed streams, which are internal ones
+ if(i->StreamNameLength > 1 && i->StreamName[0] == ':' && i->StreamName[1] != ':')
+ {
+ // Alternate data streams can themselves have alternate data streams, so filter out any starting with '$' as those are internal
+ auto length = i->StreamNameLength / sizeof(wchar_t);
+ for(auto *c = i->StreamName + length; c > i->StreamName; c--)
+ {
+ if(c[0] == '$' && c > i->StreamName && c[-1] == ':')
+ {
+ length = c - i->StreamName - 1;
+ break;
+ }
+ }
+ filled[count] = path_view_component(i->StreamName + 1, length - 1, path_view_component::not_zero_terminated);
+ count++;
+ }
+ if(i->NextEntryOffset == 0)
+ {
+ done = true;
+ }
+ else
+ {
+ p += i->NextEntryOffset;
+ }
+ } while(!done && p < tofill.data() + isb.Information);
+ }
+ return filled;
+}
+
+result<span<byte>> fs_handle::get_extended_attribute(span<byte> tofill, path_view_component name) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ auto &h = _get_handle();
+ LLFIO_LOG_FUNCTION_CALL(&h);
+ OUTCOME_TRY(auto &&attribh, detail::create_alternate_stream(h, name, handle::mode::read, handle::creation::open_existing));
+ IO_STATUS_BLOCK isb = make_iostatus();
+ NTSTATUS ntstat = NtReadFile(attribh.native_handle().h, nullptr, nullptr, nullptr, &isb, tofill.data(), (ULONG) tofill.size(), nullptr, nullptr);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(attribh.native_handle().h, isb, deadline());
+ }
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ return {tofill.data(), isb.Information};
+}
+
+result<void> fs_handle::set_extended_attribute(path_view_component name, span<const byte> value) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ auto &h = _get_handle();
+ LLFIO_LOG_FUNCTION_CALL(&h);
+ OUTCOME_TRY(auto &&attribh, detail::create_alternate_stream(h, name, handle::mode::write, handle::creation::always_new));
+ IO_STATUS_BLOCK isb = make_iostatus();
+ NTSTATUS ntstat = NtWriteFile(attribh.native_handle().h, nullptr, nullptr, nullptr, &isb, (PVOID) value.data(), (ULONG) value.size(), nullptr, nullptr);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(attribh.native_handle().h, isb, deadline());
+ }
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ if(isb.Information < value.size())
+ {
+ return errc::no_space_on_device;
+ }
+ return success();
+}
+
+result<void> fs_handle::remove_extended_attribute(path_view_component name) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ auto &h = _get_handle();
+ LLFIO_LOG_FUNCTION_CALL(&h);
+ OUTCOME_TRY(auto &&attribh, detail::create_alternate_stream(h, name, handle::mode::attr_write, handle::creation::open_existing));
+ // Mark the item as delete on close
+ IO_STATUS_BLOCK isb = make_iostatus();
+ FILE_DISPOSITION_INFORMATION fdi{};
+ memset(&fdi, 0, sizeof(fdi));
+ fdi._DeleteFile = 1u;
+ NTSTATUS ntstat = NtSetInformationFile(attribh.native_handle().h, &isb, &fdi, sizeof(fdi), FileDispositionInformation);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(attribh.native_handle().h, isb, deadline());
+ }
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ return success();
+}
+
+
+/************************************************ NTFS extended attributes *********************************************************/
+
+namespace detail
+{
+ struct win_extended_attributes_return_t
+ {
+ span<std::pair<path_view_component, span<byte>>> filled;
+ size_t tofillremaining;
+ };
+ static inline result<win_extended_attributes_return_t> win_extended_attributes(span<byte> tofill, const handle &h,
+ span<const path_view_component> names) noexcept
+ {
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ IO_STATUS_BLOCK isb = make_iostatus();
+ _FILE_GET_EA_INFORMATION *ealist = nullptr;
+ ULONG ealistlen = 0;
+ for(auto name : names)
+ {
+ if(name.native_size() > 255)
+ {
+ return errc::invalid_argument;
+ }
+ if(!visit(name, [](auto sv) { return sizeof(sv[0]) == 1; }))
+ {
+ return errc::invalid_argument;
+ }
+ ealistlen += (ULONG) ((sizeof(ULONG) + 1 + name.native_size() + 3) & ~3);
+ }
+ if(ealistlen > 0)
+ {
+ ealist = (_FILE_GET_EA_INFORMATION *) alloca(ealistlen);
+ if(ealist == nullptr)
+ {
+ return errc::not_enough_memory;
+ }
+ auto *p = (byte *) ealist;
+ auto *i = (_FILE_GET_EA_INFORMATION *) p;
+ for(auto name : names)
+ {
+ i = (_FILE_GET_EA_INFORMATION *) p;
+ i->NextEntryOffset = (ULONG) ((sizeof(ULONG) + 1 + name.native_size() + 3) & ~3);
+ i->EaNameLength = (UCHAR) name.native_size();
+ visit(name,
+ [&](auto sv)
+ {
+ memcpy(i->EaName, sv.data(), i->EaNameLength);
+ i->EaName[i->EaNameLength] = 0;
+ });
+ p += i->NextEntryOffset;
+ }
+ i->NextEntryOffset = 0;
+ }
+ NTSTATUS ntstat = NtQueryEaFile(h.native_handle().h, &isb, tofill.data(), (ULONG) tofill.size(), false, ealist, ealistlen, nullptr, false);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(h.native_handle().h, isb, {});
+ }
+ win_extended_attributes_return_t ret;
+ ret.tofillremaining = tofill.size();
+ if(ntstat < 0)
+ {
+ if(ntstat == 0xC0000052 /*STATUS_NO_EAS_ON_FILE*/)
+ {
+ return ret;
+ }
+ return ntkernel_error(ntstat);
+ }
+ {
+ auto *p = tofill.data();
+ bool done = false;
+ size_t count = 0;
+ do
+ {
+ auto *i = (_FILE_FULL_EA_INFORMATION *) p;
+ if(i->EaNameLength > 0)
+ {
+ count++;
+ }
+ if(i->NextEntryOffset == 0)
+ {
+ done = true;
+ }
+ else
+ {
+ p += i->NextEntryOffset;
+ }
+ } while(!done && p < tofill.data() + isb.Information);
+ const auto offset = (isb.Information + 7) & ~7;
+ ret.tofillremaining = tofill.size() - offset;
+ if(count * sizeof(std::pair<path_view_component, span<byte>>) > ret.tofillremaining)
+ {
+ return errc::no_buffer_space;
+ }
+ ret.filled = {(std::pair<path_view_component, span<byte>> *) (p + offset), count};
+ ret.tofillremaining -= count * sizeof(std::pair<path_view_component, span<byte>>);
+ }
+ {
+ auto *p = tofill.data();
+ bool done = false;
+ size_t count = 0;
+ do
+ {
+ auto *i = (_FILE_FULL_EA_INFORMATION *) p;
+ if(i->EaNameLength > 0)
+ {
+ ret.filled[count] = {path_view_component(i->EaName, i->EaNameLength, path_view_component::zero_terminated),
+ {(byte *) i->EaName + i->EaNameLength + 1, i->EaValueLength}};
+ count++;
+ }
+ if(i->NextEntryOffset == 0)
+ {
+ done = true;
+ }
+ else
+ {
+ p += i->NextEntryOffset;
+ }
+ } while(!done && p < tofill.data() + isb.Information);
+ }
+ return ret;
+ }
+} // namespace detail
+
+result<span<std::pair<path_view_component, span<byte>>>> fs_handle::win_list_extended_attributes(span<byte> tofill) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ auto &h = _get_handle();
+ LLFIO_LOG_FUNCTION_CALL(&h);
+ OUTCOME_TRY(auto &&ret, detail::win_extended_attributes(tofill, h, {}));
+ return ret.filled;
+}
+
+result<span<std::pair<path_view_component, span<byte>>>> fs_handle::win_get_extended_attributes(span<byte> tofill,
+ span<const path_view_component> names) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ auto &h = _get_handle();
+ LLFIO_LOG_FUNCTION_CALL(&h);
+ OUTCOME_TRY(auto &&ret, detail::win_extended_attributes(tofill, h, names));
+ return ret.filled;
+}
+
+result<void> fs_handle::win_set_extended_attributes(span<std::pair<const path_view_component, span<const byte>>> toset) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ auto &h = _get_handle();
+ LLFIO_LOG_FUNCTION_CALL(&h);
+ _FILE_FULL_EA_INFORMATION *ealist = nullptr;
+ ULONG ealistlen = 0;
+ for(auto item : toset)
+ {
+ if(item.first.native_size() > 255 || item.second.size() > 65535)
+ {
+ return errc::invalid_argument;
+ }
+ if(!visit(item.first, [](auto sv) { return sizeof(sv[0]) == 1; }))
+ {
+ return errc::invalid_argument;
+ }
+ ealistlen += (ULONG) ((sizeof(ULONG) + 4 + item.first.native_size() + 1 + item.second.size() + 3) & ~3);
+ }
+ if(ealistlen > 0)
+ {
+ ealist = (_FILE_FULL_EA_INFORMATION *) alloca(ealistlen);
+ if(ealist == nullptr)
+ {
+ return errc::not_enough_memory;
+ }
+ auto *p = (byte *) ealist;
+ auto *i = (_FILE_FULL_EA_INFORMATION *) p;
+ for(auto item : toset)
+ {
+ i = (_FILE_FULL_EA_INFORMATION *) p;
+ i->NextEntryOffset = (ULONG) ((sizeof(ULONG) + 4 + item.first.native_size() + 1 + item.second.size() + 3) & ~3);
+ i->Flags = 0;
+ i->EaNameLength = (UCHAR) item.first.native_size();
+ visit(item.first,
+ [&](auto sv)
+ {
+ memcpy(i->EaName, sv.data(), i->EaNameLength);
+ i->EaName[i->EaNameLength] = 0;
+ });
+ i->EaValueLength = (USHORT) item.second.size();
+ memcpy(i->EaName + i->EaNameLength + 1, item.second.data(), item.second.size());
+ p += i->NextEntryOffset;
+ }
+ i->NextEntryOffset = 0;
+ }
+ IO_STATUS_BLOCK isb = make_iostatus();
+ NTSTATUS ntstat = NtSetEaFile(h.native_handle().h, &isb, ealist, ealistlen);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(h.native_handle().h, isb, {});
+ }
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ return success();
+}
+
/**************************************** to_win32_path() *******************************************/
diff --git a/include/llfio/v2.0/detail/impl/windows/import.hpp b/include/llfio/v2.0/detail/impl/windows/import.hpp
index 96b6dc0a..d7dbbcc3 100644
--- a/include/llfio/v2.0/detail/impl/windows/import.hpp
+++ b/include/llfio/v2.0/detail/impl/windows/import.hpp
@@ -338,6 +338,12 @@ namespace windows_nt_kernel
using NtClose_t = NTSTATUS(NTAPI *)(_Out_ HANDLE FileHandle);
+ using NtQueryEaFile_t = NTSTATUS(NTAPI *)(_In_ HANDLE FileHandle, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _Out_ PVOID Buffer, _In_ ULONG Length,
+ _In_ BOOLEAN ReturnSingleEntry, _In_opt_ PVOID EaList, _In_ ULONG EaListLength, _In_opt_ PULONG EaIndex,
+ _In_ BOOLEAN RestartScan);
+
+ using NtSetEaFile_t = NTSTATUS(NTAPI *)(_In_ HANDLE FileHandle, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _Out_ PVOID Buffer, _In_ ULONG Length);
+
// From https://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/File/NtCreateNamedPipeFile.html
using NtCreateNamedPipeFile_t = NTSTATUS(NTAPI *)(_Out_ PHANDLE NamedPipeFileHandle, _In_ ACCESS_MASK DesiredAccess, _In_ POBJECT_ATTRIBUTES ObjectAttributes,
_Out_ PIO_STATUS_BLOCK IoStatusBlock, _In_ ULONG ShareAccess, _In_ ULONG CreateDisposition,
@@ -687,6 +693,10 @@ namespace windows_nt_kernel
FILE_ID_128 FileId;
} FILE_ID_INFORMATION, *PFILE_ID_INFORMATION;
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4201) // nameless struct/union
+#endif
typedef struct _FILE_OBJECTID_INFORMATION // NOLINT
{
LONGLONG FileReference;
@@ -702,6 +712,9 @@ namespace windows_nt_kernel
UCHAR ExtendedInfo[48];
} DUMMYUNIONNAME;
} FILE_OBJECTID_INFORMATION, *PFILE_OBJECTID_INFORMATION;
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
typedef struct _FILE_STAT_INFORMATION // NOLINT
{
@@ -718,6 +731,31 @@ namespace windows_nt_kernel
ACCESS_MASK EffectiveAccess;
} FILE_STAT_INFORMATION, *PFILE_STAT_INFORMATION;
+ typedef struct _FILE_FULL_EA_INFORMATION
+ {
+ ULONG NextEntryOffset;
+ UCHAR Flags;
+ UCHAR EaNameLength;
+ USHORT EaValueLength;
+ CHAR EaName[1];
+ } FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION;
+
+ typedef struct _FILE_GET_EA_INFORMATION
+ {
+ ULONG NextEntryOffset;
+ UCHAR EaNameLength;
+ CHAR EaName[1];
+ } FILE_GET_EA_INFORMATION, *PFILE_GET_EA_INFORMATION;
+
+ typedef struct _FILE_STREAM_INFORMATION
+ {
+ ULONG NextEntryOffset;
+ ULONG StreamNameLength;
+ LARGE_INTEGER StreamSize;
+ LARGE_INTEGER StreamAllocationSize;
+ WCHAR StreamName[1];
+ } FILE_STREAM_INFORMATION, *PFILE_STREAM_INFORMATION;
+
typedef struct _FILE_FS_ATTRIBUTE_INFORMATION // NOLINT
{
ULONG FileSystemAttributes;
@@ -991,6 +1029,8 @@ namespace windows_nt_kernel
static NtCancelIoFileEx_t NtCancelIoFileEx;
static NtDeleteFile_t NtDeleteFile;
static NtClose_t NtClose;
+ static NtQueryEaFile_t NtQueryEaFile;
+ static NtSetEaFile_t NtSetEaFile;
static NtCreateNamedPipeFile_t NtCreateNamedPipeFile;
static NtQueryDirectoryFile_t NtQueryDirectoryFile;
static NtSetInformationFile_t NtSetInformationFile;
@@ -1150,6 +1190,20 @@ namespace windows_nt_kernel
abort();
}
}
+ if(NtQueryEaFile == nullptr)
+ {
+ if((NtQueryEaFile = reinterpret_cast<NtQueryEaFile_t>(GetProcAddress(ntdllh, "NtQueryEaFile"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtSetEaFile == nullptr)
+ {
+ if((NtSetEaFile = reinterpret_cast<NtSetEaFile_t>(GetProcAddress(ntdllh, "NtSetEaFile"))) == nullptr)
+ {
+ abort();
+ }
+ }
if(NtCreateNamedPipeFile == nullptr)
{
if((NtCreateNamedPipeFile = reinterpret_cast<NtCreateNamedPipeFile_t>(GetProcAddress(ntdllh, "NtCreateNamedPipeFile"))) == nullptr)
diff --git a/include/llfio/v2.0/fs_handle.hpp b/include/llfio/v2.0/fs_handle.hpp
index f6a6630b..1b611290 100644
--- a/include/llfio/v2.0/fs_handle.hpp
+++ b/include/llfio/v2.0/fs_handle.hpp
@@ -1,5 +1,5 @@
/* A filing system handle
-(C) 2017-2020 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
+(C) 2017-2022 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
File Created: Aug 2017
@@ -246,7 +246,7 @@ public:
return ret;
}
- /*! Obtain a handle to the path **currently** containing this handle's file entry.
+ /*! \brief Obtain a handle to the path **currently** containing this handle's file entry.
\warning This call is \b racy and can result in the wrong path handle being returned. Note that
unless `flag::disable_safety_unlinks` is set, this implementation opens a
@@ -263,7 +263,7 @@ public:
LLFIO_DEADLINE_TRY_FOR_UNTIL(parent_path_handle)
- /*! Relinks the current path of this open handle to the new path specified. If `atomic_replace` is
+ /*! \brief Relinks the current path of this open handle to the new path specified. If `atomic_replace` is
true, the relink \b atomically and silently replaces any item at the new path specified. This
operation is both atomic and matching POSIX behaviour even on Microsoft Windows where
no Win32 API can match POSIX semantics.
@@ -304,7 +304,7 @@ public:
LLFIO_DEADLINE_TRY_FOR_UNTIL(relink)
- /*! Links the inode referred to by this open handle to the path specified. The current path
+ /*! \brief Links the inode referred to by this open handle to the path specified. The current path
of this open handle is not changed, unless it has no current path due to being unlinked.
\warning Some operating systems provide a race free syscall for linking an open handle to a new
@@ -329,8 +329,10 @@ public:
LLFIO_DEADLINE_TRY_FOR_UNTIL(link)
- /*! Unlinks the current path of this open handle, causing its entry to immediately disappear
- from the filing system. On Windows before Windows 10 1709 unless
+ /*! \brief Unlinks the current path of this open handle, causing its entry to immediately disappear
+ from the filing system.
+
+ On Windows before Windows 10 1709 unless
`flag::win_disable_unlink_emulation` is set, this behaviour is simulated by renaming the file
to something random and setting its delete-on-last-close flag. Note that Windows may prevent
the renaming of a file in use by another process, if so it will NOT be renamed.
@@ -356,6 +358,93 @@ public:
result<void> unlink(deadline d = std::chrono::seconds(30)) noexcept;
LLFIO_DEADLINE_TRY_FOR_UNTIL(unlink)
+
+ /*! \brief Fill the supplied buffer with the names of all extended attributes set on this file or directory,
+ returning a span of pairs of path view components and values into that buffer.
+
+ Note that this routine is a very thin wrap of `listxattr()` on POSIX and `NtQueryInformationFile()`
+ on Windows. If the supplied buffer is too small, the syscall typically returns failure rather
+ than do a partial fill. Most implementations do not support more than 64Kb of extended attribute
+ information per inode so maybe 70Kb is a safe default (to account for the return value storage),
+ however properly written code will detect the buffer being too small and will auto-expand it until
+ success.
+
+ \note On Windows, this is the list of alternate streams on a file, NOT NTFS extended attributes.
+
+ \raceguarantees The list of extended attributes is fetched in a single syscall. This may be an
+ atomically consistent snapshot.
+ */
+ LLFIO_HEADERS_ONLY_VIRTUAL_SPEC
+ result<span<path_view_component>> list_extended_attributes(span<byte> tofill) noexcept;
+
+ /*! \brief Retrieve the value of an extended attribute set on this file or directory.
+
+ \note On Windows, this is the list of alternate streams on a file, NOT NTFS extended attributes.
+ */
+ LLFIO_HEADERS_ONLY_VIRTUAL_SPEC
+ result<span<byte>> get_extended_attribute(span<byte> tofill, path_view_component name) noexcept;
+
+ /*! \brief Sets the value of an extended attribute on this file or directory.
+
+ To prevent collision in a globally visible resource, there is a convention whereby you ought to
+ namespace the names of your values as `namespace.attribute`
+ e.g. `appname.setting` to prevent unintentional collision with other programs. Obviously, do choose
+ a unique `appname` if there is any chance another program might use the same namespace name.
+
+ On POSIX, there are additional namespacing requirements: before your value name, you need to prefix
+ one of `user` or `system`, so the actual name you might set would be `user.appname.propname`.
+ Windows does not have the `user`/`system` prefix requirement, but it does no harm to do the exact
+ same on Windows as on POSIX.
+
+ The host OS and target filing system choose the limits on value size, and will fail accordingly.
+ Some impose a maximum of 64Kb for all names and values per inode, others have a 4Kb maximum
+ value size, there are lots of combinations. You are probably safest not setting many names,
+ and keep the values short.
+
+ \warning Extended attributes are 'brittle' because they can get silently wiped at any moment.
+ Never store anything in extended attributes which cannot be recalculated if missing. The ideal
+ use case for extended attributes is as a cache of additional metadata about a file or
+ directory e.g. "I last checked this directory at timestamp X", or "the MD5 hash at last modified
+ timestamp X for this file was Y". Also remember that other
+ processes can and do arbitrarily modify extended attributes concurrent to you.
+
+ ### Windows only
+
+ This API is implemented as file alternate data streams, rather than the Extended Attributes
+ API as accessed via `NtSetEaFile()` and `NtQueryEaFile()` (which actually modify the file
+ alternate data stream `::$EA` in any case).
+
+ The reason why is that `NtSetEaFile()` can only **append** new records to EA storage. It cannot deallocate
+ any existing EA records, if you try to do so you will get `STATUS_EA_CORRUPT_ERROR`. You can
+ append setting the same name to a different value, which can include a null value which then
+ appears as if the name is no longer there. But there is a cap of 64kB for the EA record, and
+ once it is consumed, it is gone forever for that inode.
+
+ Obviously that doesn't map at all well onto POSIX extended attributes, where you can set the
+ value of an attribute as frequently as you like. The closest equivalent on Windows is therefore
+ file alternate data streams, even though the attribute's value is then worked with as a whole
+ proper file with all the attendant performance consequences.
+
+ As a result, `name` must be a valid filename and not contain any characters not permitted in
+ a filename. We use the NT API here, so the character restrictions are far fewer than for the
+ Win32 API e.g. single character names do NOT cause misoperation like on Win32.
+ */
+ LLFIO_HEADERS_ONLY_VIRTUAL_SPEC
+ result<void> set_extended_attribute(path_view_component name, span<const byte> value) noexcept;
+
+ /*! \brief Removes the extended attribute set on this file or directory.
+ */
+ LLFIO_HEADERS_ONLY_VIRTUAL_SPEC
+ result<void> remove_extended_attribute(path_view_component) noexcept;
+
+#ifdef _WIN32
+ //! Windows only: List all the NTFS extended attributes on a file. See the documentation for `set_extended_attribute()` before use.
+ result<span<std::pair<path_view_component, span<byte>>>> win_list_extended_attributes(span<byte> tofill) noexcept;
+ //! Windows only: Get the values of NTFS extended attributes on a file. See the documentation for `set_extended_attribute()` before use.
+ result<span<std::pair<path_view_component, span<byte>>>> win_get_extended_attributes(span<byte> tofill, span<const path_view_component> names) noexcept;
+ //! Windows only: Set the values of a NTFS extended attributes on a file. See the documentation for `set_extended_attribute()` before use. In particular, note the requirement that you can only _extend_ the attributes list i.e. you must always set whatever the list is already, with additional members.
+ result<void> win_set_extended_attributes(span<std::pair<const path_view_component, span<const byte>>> toset) noexcept;
+#endif
};
namespace detail
diff --git a/include/llfio/v2.0/path_view.hpp b/include/llfio/v2.0/path_view.hpp
index cc42d79e..a43c0e01 100644
--- a/include/llfio/v2.0/path_view.hpp
+++ b/include/llfio/v2.0/path_view.hpp
@@ -1385,7 +1385,9 @@ public:
//! The size of the internal buffer
static constexpr size_t internal_buffer_size() noexcept { return (_internal_buffer_size > 0) ? _internal_buffer_size : 1; }
//! The storage capacity, which may be larger than `size()` if the internal buffer is in use
- size_t capacity() noexcept { return (this->data() == _buffer) ? internal_buffer_size() : this->size(); }
+ size_t capacity() const noexcept { return (this->data() == _buffer) ? internal_buffer_size() : this->size(); }
+ //! True if this rendered path refers to the source path view
+ bool references_source() const noexcept { return this->data() != _buffer && _bytes_to_delete == 0; }
//! Access the custom deleter instance passed to the constructor
const AllocatorOrDeleter &deleter() const noexcept { return _deleter2; }
diff --git a/test/tests/extended_attributes.cpp b/test/tests/extended_attributes.cpp
new file mode 100644
index 00000000..febbab5f
--- /dev/null
+++ b/test/tests/extended_attributes.cpp
@@ -0,0 +1,84 @@
+/* Integration test kernel for whether extended_attributes() works
+(C) 2022 Niall Douglas <http://www.nedproductions.biz/> (2 commits)
+File Created: Feb 2022
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../test_kernel_decl.hpp"
+
+static inline void TestExtendedAttributes()
+{
+ namespace llfio = LLFIO_V2_NAMESPACE;
+ llfio::byte buffer[65536];
+ auto fh = llfio::file_handle::temp_file().value();
+ std::cout << "NOTE: The temporary file for this test can be found at " << fh.current_path().value() << std::endl;
+#ifdef _WIN32
+ static const llfio::path_view_component name[] = {L"user.llfiotest.name2", L"user.llfiotest.name1", L"user.llfiotest.name3"};
+#else
+ static const llfio::path_view_component name[] = {"user.llfiotest.name2", "user.llfiotest.name1", "user.llfiotest.name3"};
+#endif
+ {
+ auto attribs = fh.list_extended_attributes(buffer).value();
+ BOOST_CHECK(attribs.size() == 0);
+ }
+ {
+ fh.set_extended_attribute(name[2], llfio::span<const llfio::byte>{(const llfio::byte *) "I love you!", 11}).value();
+ }
+ {
+ auto attribs = fh.list_extended_attributes(buffer).value();
+ BOOST_REQUIRE(attribs.size() == 1);
+ BOOST_CHECK(attribs.end() == std::find_if(attribs.begin(), attribs.end(), [](const auto &i) { return i == name[0]; }));
+ BOOST_CHECK(attribs.end() == std::find_if(attribs.begin(), attribs.end(), [](const auto &i) { return i == name[1]; }));
+ BOOST_CHECK(attribs.end() != std::find_if(attribs.begin(), attribs.end(), [](const auto &i) { return i == name[2]; }));
+ }
+ {
+ fh.set_extended_attribute(name[0], llfio::span<const llfio::byte>{(const llfio::byte *) "World", 5}).value();
+ fh.set_extended_attribute(name[1], llfio::span<const llfio::byte>{(const llfio::byte *) "Hello", 5}).value();
+ }
+ {
+ auto attribs = fh.list_extended_attributes(buffer).value();
+ BOOST_REQUIRE(attribs.size() == 3);
+ BOOST_CHECK(attribs.end() != std::find_if(attribs.begin(), attribs.end(), [](const auto &i) { return i == name[0]; }));
+ BOOST_CHECK(attribs.end() != std::find_if(attribs.begin(), attribs.end(), [](const auto &i) { return i == name[1]; }));
+ BOOST_CHECK(attribs.end() != std::find_if(attribs.begin(), attribs.end(), [](const auto &i) { return i == name[2]; }));
+ }
+ {
+ auto value1 = fh.get_extended_attribute(buffer, name[1]).value();
+ BOOST_REQUIRE(value1.size() == 5);
+ BOOST_CHECK(0 == memcmp(value1.data(), "Hello", 5));
+ auto value2 = fh.get_extended_attribute(buffer, name[0]).value();
+ BOOST_REQUIRE(value2.size() == 5);
+ BOOST_CHECK(0 == memcmp(value2.data(), "World", 5));
+ auto value3 = fh.get_extended_attribute(buffer, name[2]).value();
+ BOOST_REQUIRE(value3.size() == 11);
+ BOOST_CHECK(0 == memcmp(value3.data(), "I love you!", 11));
+ }
+ {
+ fh.remove_extended_attribute(name[0]).value();
+ auto attribs = fh.list_extended_attributes(buffer).value();
+ BOOST_REQUIRE(attribs.size() == 2);
+ BOOST_CHECK(attribs.end() == std::find_if(attribs.begin(), attribs.end(), [](const auto &i) { return i == name[0]; }));
+ BOOST_CHECK(attribs.end() != std::find_if(attribs.begin(), attribs.end(), [](const auto &i) { return i == name[1]; }));
+ BOOST_CHECK(attribs.end() != std::find_if(attribs.begin(), attribs.end(), [](const auto &i) { return i == name[2]; }));
+ }
+}
+
+KERNELTEST_TEST_KERNEL(integration, llfio, extended_attributes, works, "Tests that extended_attributes() works as expected", TestExtendedAttributes())