diff options
author | Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com) <spamtrap@nedprod.com> | 2017-09-20 03:40:29 +0300 |
---|---|---|
committer | Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com) <spamtrap@nedprod.com> | 2017-09-20 03:40:29 +0300 |
commit | 61148a3e84837a7e7c433b893916ace9404106b6 (patch) | |
tree | 6dc60e915a22aa96b64ebba7e8a03bebc09d40f8 | |
parent | 552867a000005b13d170c6cbd36ab0f476d9f805 (diff) |
handle::clone() can now optionally reopen the handle with new privs. Thanks to Bruce Adams from std-proposals for suggesting this.
-rw-r--r-- | Readme.md | 1 | ||||
-rw-r--r-- | include/afio/revision.hpp | 6 | ||||
-rw-r--r-- | include/afio/v2.0/async_file_handle.hpp | 16 | ||||
-rw-r--r-- | include/afio/v2.0/detail/impl/posix/async_file_handle.ipp | 8 | ||||
-rw-r--r-- | include/afio/v2.0/detail/impl/posix/directory_handle.ipp | 65 | ||||
-rw-r--r-- | include/afio/v2.0/detail/impl/posix/file_handle.ipp | 113 | ||||
-rw-r--r-- | include/afio/v2.0/detail/impl/posix/handle.ipp | 55 | ||||
-rw-r--r-- | include/afio/v2.0/detail/impl/posix/mapped_file_handle.ipp | 2 | ||||
-rw-r--r-- | include/afio/v2.0/detail/impl/windows/async_file_handle.ipp | 8 | ||||
-rw-r--r-- | include/afio/v2.0/detail/impl/windows/directory_handle.ipp | 63 | ||||
-rw-r--r-- | include/afio/v2.0/detail/impl/windows/file_handle.ipp | 45 | ||||
-rw-r--r-- | include/afio/v2.0/detail/impl/windows/handle.ipp | 22 | ||||
-rw-r--r-- | include/afio/v2.0/directory_handle.hpp | 11 | ||||
-rw-r--r-- | include/afio/v2.0/file_handle.hpp | 12 | ||||
-rw-r--r-- | include/afio/v2.0/handle.hpp | 11 | ||||
-rw-r--r-- | include/afio/v2.0/mapped_file_handle.hpp | 11 | ||||
-rw-r--r-- | test/tests/directory_handle_create_close/runner.cpp | 5 |
17 files changed, 290 insertions, 164 deletions
@@ -6,7 +6,6 @@ Tarballs of source and prebuilt binaries for Linux x64 and Windows x64: http://m ### Immediate todos in order of priority: -- [ ] Implement file/directory handle reopening. - [ ] `atomic_append` isn't actually being tested in shared_fs_mutex - [ ] Implement a non-toy ACID key-value BLOB store and send it to Boost for peer review. - [ ] All time based kernel tests need to use soak test based API and auto adjust to diff --git a/include/afio/revision.hpp b/include/afio/revision.hpp index 03a4292f..4233b8bc 100644 --- a/include/afio/revision.hpp +++ b/include/afio/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 AFIO_PREVIOUS_COMMIT_REF 5e0683d452700941c589f10dcfb8ead5409ed20d -#define AFIO_PREVIOUS_COMMIT_DATE "2017-09-19 22:35:48 +00:00" -#define AFIO_PREVIOUS_COMMIT_UNIQUE 5e0683d4 +#define AFIO_PREVIOUS_COMMIT_REF 552867a000005b13d170c6cbd36ab0f476d9f805 +#define AFIO_PREVIOUS_COMMIT_DATE "2017-09-19 23:11:42 +00:00" +#define AFIO_PREVIOUS_COMMIT_UNIQUE 552867a0 diff --git a/include/afio/v2.0/async_file_handle.hpp b/include/afio/v2.0/async_file_handle.hpp index 5d5dd56f..550c182d 100644 --- a/include/afio/v2.0/async_file_handle.hpp +++ b/include/afio/v2.0/async_file_handle.hpp @@ -216,8 +216,20 @@ public: \errors Any of the values POSIX dup() or DuplicateHandle() can return. */ - AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<async_file_handle> clone(io_service &service) const noexcept; - using file_handle::clone; + result<async_file_handle> clone(io_service &service, mode _mode = mode::unchanged, caching _caching = caching::unchanged, deadline d = std::chrono::seconds(30)) const noexcept + { + OUTCOME_TRY(v, file_handle::clone(_mode, _caching, d)); + async_file_handle ret(std::move(v)); + ret._service = &service; + return std::move(ret); + } + AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<file_handle> clone(mode _mode = mode::unchanged, caching _caching = caching::unchanged, deadline d = std::chrono::seconds(30)) const noexcept override + { + OUTCOME_TRY(v, file_handle::clone(_mode, _caching, d)); + async_file_handle ret(std::move(v)); + ret._service = _service; + return static_cast<file_handle &&>(ret); + } #if DOXYGEN_SHOULD_SKIP_THIS private: diff --git a/include/afio/v2.0/detail/impl/posix/async_file_handle.ipp b/include/afio/v2.0/detail/impl/posix/async_file_handle.ipp index a99bca2b..52e8222c 100644 --- a/include/afio/v2.0/detail/impl/posix/async_file_handle.ipp +++ b/include/afio/v2.0/detail/impl/posix/async_file_handle.ipp @@ -58,14 +58,6 @@ async_file_handle::io_result<async_file_handle::const_buffers_type> async_file_h return *ret; } -result<async_file_handle> async_file_handle::clone(io_service &service) const noexcept -{ - OUTCOME_TRY(v, clone()); - async_file_handle ret(std::move(v)); - ret._service = &service; - return std::move(ret); -} - template <class CompletionRoutine, class BuffersType, class IORoutine> result<async_file_handle::io_state_ptr<CompletionRoutine, BuffersType>> async_file_handle::_begin_io(async_file_handle::operation_t operation, async_file_handle::io_request<BuffersType> reqs, CompletionRoutine &&completion, IORoutine && /*ioroutine*/) noexcept { diff --git a/include/afio/v2.0/detail/impl/posix/directory_handle.ipp b/include/afio/v2.0/detail/impl/posix/directory_handle.ipp index 37205343..578adfea 100644 --- a/include/afio/v2.0/detail/impl/posix/directory_handle.ipp +++ b/include/afio/v2.0/detail/impl/posix/directory_handle.ipp @@ -41,8 +41,10 @@ AFIO_V2_NAMESPACE_BEGIN result<directory_handle> directory_handle::directory(const path_handle &base, path_view_type path, mode _mode, creation _creation, caching _caching, flag flags) noexcept { - // We don't implement unlink on close - flags &= ~flag::unlink_on_close; + if(flags & flag::unlink_on_close) + { + return std::errc::invalid_argument; + } result<directory_handle> ret(directory_handle(native_handle_type(), 0, 0, _caching, flags)); native_handle_type &nativeh = ret.value()._v; AFIO_LOG_FUNCTION_CALL(&ret); @@ -116,15 +118,60 @@ result<directory_handle> directory_handle::directory(const path_handle &base, pa return ret; } -result<directory_handle> directory_handle::clone() const noexcept +result<directory_handle> directory_handle::clone(mode mode_, caching caching_, deadline d) const noexcept { AFIO_LOG_FUNCTION_CALL(this); - result<directory_handle> ret(directory_handle(native_handle_type(), _devid, _inode, _caching, _flags)); - ret.value()._v.behaviour = _v.behaviour; - ret.value()._v.fd = ::dup(_v.fd); - if(-1 == ret.value()._v.fd) - return {errno, std::system_category()}; - return ret; + // Fast path + if(mode_ == mode::unchanged && caching_ == caching::unchanged) + { + result<directory_handle> ret(directory_handle(native_handle_type(), _devid, _inode, _caching, _flags)); + ret.value()._v.behaviour = _v.behaviour; + ret.value()._v.fd = ::dup(_v.fd); + if(-1 == ret.value()._v.fd) + return {errno, std::system_category()}; + return ret; + } + // Slow path + std::chrono::steady_clock::time_point began_steady; + std::chrono::system_clock::time_point end_utc; + if(d) + { + if(d.steady) + began_steady = std::chrono::steady_clock::now(); + else + end_utc = d.to_time_point(); + } + for(;;) + { + // Get the current path of myself + OUTCOME_TRY(currentpath, current_path()); + // Open myself + auto fh = directory({}, currentpath, mode_, creation::open_existing, caching_, _flags); + if(fh) + { + if(fh.value().unique_id() == unique_id()) + return fh; + } + else + { + if(fh.error() != std::errc::no_such_file_or_directory) + return fh.error(); + } + // Check timeout + if(d) + { + if(d.steady) + { + if(std::chrono::steady_clock::now() >= (began_steady + std::chrono::nanoseconds(d.nsecs))) + return std::errc::timed_out; + } + else + { + if(std::chrono::system_clock::now() >= end_utc) + return std::errc::timed_out; + } + } + } } result<directory_handle::enumerate_info> directory_handle::enumerate(buffers_type &&tofill, path_view_type glob, filter /*unused*/, span<char> kernelbuffer) const noexcept diff --git a/include/afio/v2.0/detail/impl/posix/file_handle.ipp b/include/afio/v2.0/detail/impl/posix/file_handle.ipp index 6b193f90..1f2bc58e 100644 --- a/include/afio/v2.0/detail/impl/posix/file_handle.ipp +++ b/include/afio/v2.0/detail/impl/posix/file_handle.ipp @@ -163,16 +163,113 @@ file_handle::io_result<file_handle::const_buffers_type> file_handle::barrier(fil return io_handle::io_result<const_buffers_type>(std::move(reqs.buffers)); } -result<file_handle> file_handle::clone() const noexcept +result<file_handle> file_handle::clone(mode mode_, caching caching_, deadline d) const noexcept { AFIO_LOG_FUNCTION_CALL(this); - result<file_handle> ret(file_handle(native_handle_type(), _devid, _inode, _caching, _flags)); - ret.value()._service = _service; - ret.value()._v.behaviour = _v.behaviour; - ret.value()._v.fd = ::dup(_v.fd); - if(-1 == ret.value()._v.fd) - return {errno, std::system_category()}; - return ret; + // Fast path + if(mode_ == mode::unchanged) + { + result<file_handle> ret(file_handle(native_handle_type(), _devid, _inode, caching_, _flags)); + ret.value()._service = _service; + ret.value()._v.behaviour = _v.behaviour; + ret.value()._v.fd = ::dup(_v.fd); + if(-1 == ret.value()._v.fd) + return {errno, std::system_category()}; + if(caching_ == caching::unchanged) + return ret; + + int attribs = fcntl(ret.value()._v.fd, F_GETFL); + if(-1 != attribs) + { + attribs &= ~(O_SYNC | O_DIRECT +#ifdef O_DSYNC + | O_DSYNC +#endif + ); + switch(caching_) + { + case caching::unchanged: + break; + case caching::none: + attribs |= O_SYNC | O_DIRECT; + if(-1 == fcntl(ret.value()._v.fd, F_SETFL, attribs)) + return {errno, std::system_category()}; + ret.value()._v.behaviour |= native_handle_type::disposition::aligned_io; + break; + case caching::only_metadata: + attribs |= O_DIRECT; + if(-1 == fcntl(ret.value()._v.fd, F_SETFL, attribs)) + return {errno, std::system_category()}; + ret.value()._v.behaviour |= native_handle_type::disposition::aligned_io; + break; + case caching::reads: + attribs |= O_SYNC; + if(-1 == fcntl(ret.value()._v.fd, F_SETFL, attribs)) + return {errno, std::system_category()}; + ret.value()._v.behaviour &= ~native_handle_type::disposition::aligned_io; + break; + case caching::reads_and_metadata: +#ifdef O_DSYNC + attribs |= O_DSYNC; +#else + attribs |= O_SYNC; +#endif + if(-1 == fcntl(ret.value()._v.fd, F_SETFL, attribs)) + return {errno, std::system_category()}; + ret.value()._v.behaviour &= ~native_handle_type::disposition::aligned_io; + break; + case caching::all: + case caching::safety_fsyncs: + case caching::temporary: + if(-1 == fcntl(ret.value()._v.fd, F_SETFL, attribs)) + return {errno, std::system_category()}; + ret.value()._v.behaviour &= ~native_handle_type::disposition::aligned_io; + break; + } + return ret; + } + } + // Slow path + std::chrono::steady_clock::time_point began_steady; + std::chrono::system_clock::time_point end_utc; + if(d) + { + if(d.steady) + began_steady = std::chrono::steady_clock::now(); + else + end_utc = d.to_time_point(); + } + for(;;) + { + // Get the current path of myself + OUTCOME_TRY(currentpath, current_path()); + // Open myself + auto fh = file({}, currentpath, mode_, creation::open_existing, caching_, _flags); + if(fh) + { + if(fh.value().unique_id() == unique_id()) + return fh; + } + else + { + if(fh.error() != std::errc::no_such_file_or_directory) + return fh.error(); + } + // Check timeout + if(d) + { + if(d.steady) + { + if(std::chrono::steady_clock::now() >= (began_steady + std::chrono::nanoseconds(d.nsecs))) + return std::errc::timed_out; + } + else + { + if(std::chrono::system_clock::now() >= end_utc) + return std::errc::timed_out; + } + } + } } result<file_handle::extent_type> file_handle::length() const noexcept diff --git a/include/afio/v2.0/detail/impl/posix/handle.ipp b/include/afio/v2.0/detail/impl/posix/handle.ipp index 6dff6690..c1e865b7 100644 --- a/include/afio/v2.0/detail/impl/posix/handle.ipp +++ b/include/afio/v2.0/detail/impl/posix/handle.ipp @@ -174,59 +174,4 @@ result<void> handle::set_append_only(bool enable) noexcept return success(); } -result<void> handle::set_kernel_caching(caching caching) noexcept -{ - AFIO_LOG_FUNCTION_CALL(this); - int attribs = fcntl(_v.fd, F_GETFL); - if(-1 == attribs) - return {errno, std::system_category()}; - attribs &= ~(O_SYNC | O_DIRECT -#ifdef O_DSYNC - | O_DSYNC -#endif - ); - switch(_caching) - { - case handle::caching::unchanged: - break; - case handle::caching::none: - attribs |= O_SYNC | O_DIRECT; - if(-1 == fcntl(_v.fd, F_SETFL, attribs)) - return {errno, std::system_category()}; - _v.behaviour |= native_handle_type::disposition::aligned_io; - break; - case handle::caching::only_metadata: - attribs |= O_DIRECT; - if(-1 == fcntl(_v.fd, F_SETFL, attribs)) - return {errno, std::system_category()}; - _v.behaviour |= native_handle_type::disposition::aligned_io; - break; - case handle::caching::reads: - attribs |= O_SYNC; - if(-1 == fcntl(_v.fd, F_SETFL, attribs)) - return {errno, std::system_category()}; - _v.behaviour &= ~native_handle_type::disposition::aligned_io; - break; - case handle::caching::reads_and_metadata: -#ifdef O_DSYNC - attribs |= O_DSYNC; -#else - attribs |= O_SYNC; -#endif - if(-1 == fcntl(_v.fd, F_SETFL, attribs)) - return {errno, std::system_category()}; - _v.behaviour &= ~native_handle_type::disposition::aligned_io; - break; - case handle::caching::all: - case handle::caching::safety_fsyncs: - case handle::caching::temporary: - if(-1 == fcntl(_v.fd, F_SETFL, attribs)) - return {errno, std::system_category()}; - _v.behaviour &= ~native_handle_type::disposition::aligned_io; - break; - } - _caching = caching; - return success(); -} - AFIO_V2_NAMESPACE_END diff --git a/include/afio/v2.0/detail/impl/posix/mapped_file_handle.ipp b/include/afio/v2.0/detail/impl/posix/mapped_file_handle.ipp index 0eac991e..b63f3542 100644 --- a/include/afio/v2.0/detail/impl/posix/mapped_file_handle.ipp +++ b/include/afio/v2.0/detail/impl/posix/mapped_file_handle.ipp @@ -33,7 +33,7 @@ result<mapped_file_handle::size_type> mapped_file_handle::reserve(size_type rese OUTCOME_TRY(length, underlying_file_length()); if(length == 0) { - return std::errc::invalid_argument; + return std::errc::invalid_seek; } if(reservation == 0) { diff --git a/include/afio/v2.0/detail/impl/windows/async_file_handle.ipp b/include/afio/v2.0/detail/impl/windows/async_file_handle.ipp index ce8dcfd4..59d4b397 100644 --- a/include/afio/v2.0/detail/impl/windows/async_file_handle.ipp +++ b/include/afio/v2.0/detail/impl/windows/async_file_handle.ipp @@ -33,14 +33,6 @@ async_file_handle::io_result<async_file_handle::const_buffers_type> async_file_h return file_handle::barrier(std::move(reqs), wait_for_device, and_metadata, std::move(d)); } -result<async_file_handle> async_file_handle::clone(io_service &service) const noexcept -{ - OUTCOME_TRY(v, clone()); - async_file_handle ret(std::move(v)); - ret._service = &service; - return std::move(ret); -} - template <class CompletionRoutine, class BuffersType, class IORoutine> result<async_file_handle::io_state_ptr<CompletionRoutine, BuffersType>> async_file_handle::_begin_io(async_file_handle::operation_t operation, async_file_handle::io_request<BuffersType> reqs, CompletionRoutine &&completion, IORoutine &&ioroutine) noexcept { diff --git a/include/afio/v2.0/detail/impl/windows/directory_handle.ipp b/include/afio/v2.0/detail/impl/windows/directory_handle.ipp index 1f27c85b..6c849dcd 100644 --- a/include/afio/v2.0/detail/impl/windows/directory_handle.ipp +++ b/include/afio/v2.0/detail/impl/windows/directory_handle.ipp @@ -31,8 +31,10 @@ result<directory_handle> directory_handle::directory(const path_handle &base, pa { windows_nt_kernel::init(); using namespace windows_nt_kernel; - // We don't implement unlink on close - flags &= ~flag::unlink_on_close; + if(flags & flag::unlink_on_close) + { + return std::errc::invalid_argument; + } result<directory_handle> ret(directory_handle(native_handle_type(), 0, 0, _caching, flags)); native_handle_type &nativeh = ret.value()._v; AFIO_LOG_FUNCTION_CALL(&ret); @@ -134,27 +136,52 @@ result<directory_handle> directory_handle::directory(const path_handle &base, pa ret.value()._flags &= ~flag::disable_safety_unlinks; } } - if(flags & flag::unlink_on_close) - { - // Hide this item - IO_STATUS_BLOCK isb = make_iostatus(); - FILE_BASIC_INFORMATION fbi; - memset(&fbi, 0, sizeof(fbi)); - fbi.FileAttributes = FILE_ATTRIBUTE_HIDDEN; - NtSetInformationFile(nativeh.h, &isb, &fbi, sizeof(fbi), FileBasicInformation); - if(flags & flag::overlapped) - ntwait(nativeh.h, isb, deadline()); - } return ret; } -result<directory_handle> directory_handle::clone() const noexcept +result<directory_handle> directory_handle::clone(mode mode_, caching caching_, deadline /* unused */) const noexcept { AFIO_LOG_FUNCTION_CALL(this); - result<directory_handle> ret(directory_handle(native_handle_type(), _devid, _inode, _caching, _flags)); - ret.value()._v.behaviour = _v.behaviour; - if(!DuplicateHandle(GetCurrentProcess(), _v.h, GetCurrentProcess(), &ret.value()._v.h, 0, false, DUPLICATE_SAME_ACCESS)) - return {GetLastError(), std::system_category()}; + // Fast path + if(mode_ == mode::unchanged && caching_ == caching::unchanged) + { + result<directory_handle> ret(directory_handle(native_handle_type(), _devid, _inode, _caching, _flags)); + ret.value()._v.behaviour = _v.behaviour; + if(!DuplicateHandle(GetCurrentProcess(), _v.h, GetCurrentProcess(), &ret.value()._v.h, 0, false, DUPLICATE_SAME_ACCESS)) + return {GetLastError(), std::system_category()}; + return ret; + } + // Slow path + windows_nt_kernel::init(); + using namespace windows_nt_kernel; + result<directory_handle> ret(directory_handle(native_handle_type(), _devid, _inode, caching_, _flags)); + native_handle_type &nativeh = ret.value()._v; + nativeh.behaviour |= native_handle_type::disposition::directory; + DWORD fileshare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + OUTCOME_TRY(access, access_mask_from_handle_mode(nativeh, mode_, _flags)); + OUTCOME_TRYV(attributes_from_handle_caching_and_flags(nativeh, caching_, _flags)); + /* It is super important that we remove the DELETE permission for directories as otherwise relative renames + will always fail due to an unfortunate design choice by Microsoft. + */ + access &= ~DELETE; + OUTCOME_TRY(ntflags, ntflags_from_handle_caching_and_flags(nativeh, caching_, _flags)); + ntflags |= 0x01 /*FILE_DIRECTORY_FILE*/; // required to open a directory + OBJECT_ATTRIBUTES oa; + memset(&oa, 0, sizeof(oa)); + oa.Length = sizeof(OBJECT_ATTRIBUTES); + // It is entirely undocumented that this is how you clone a file handle with new privs + UNICODE_STRING _path; + memset(&_path, 0, sizeof(_path)); + oa.ObjectName = &_path; + oa.RootDirectory = _v.h; + IO_STATUS_BLOCK isb = make_iostatus(); + NTSTATUS ntstat = NtOpenFile(&nativeh.h, access, &oa, &isb, fileshare, ntflags); + if(STATUS_PENDING == ntstat) + ntstat = ntwait(nativeh.h, isb, deadline()); + if(ntstat < 0) + { + return {(int) ntstat, ntkernel_category()}; + } return ret; } diff --git a/include/afio/v2.0/detail/impl/windows/file_handle.ipp b/include/afio/v2.0/detail/impl/windows/file_handle.ipp index 1607bb0e..62536aa3 100644 --- a/include/afio/v2.0/detail/impl/windows/file_handle.ipp +++ b/include/afio/v2.0/detail/impl/windows/file_handle.ipp @@ -288,15 +288,46 @@ file_handle::io_result<file_handle::const_buffers_type> file_handle::barrier(fil return io_handle::io_result<const_buffers_type>(std::move(reqs.buffers)); } - -result<file_handle> file_handle::clone() const noexcept +result<file_handle> file_handle::clone(mode mode_, caching caching_, deadline /*unused*/) const noexcept { AFIO_LOG_FUNCTION_CALL(this); - result<file_handle> ret(file_handle(native_handle_type(), _devid, _inode, _caching, _flags)); - ret.value()._service = _service; - ret.value()._v.behaviour = _v.behaviour; - if(!DuplicateHandle(GetCurrentProcess(), _v.h, GetCurrentProcess(), &ret.value()._v.h, 0, false, DUPLICATE_SAME_ACCESS)) - return {GetLastError(), std::system_category()}; + // Fast path + if(mode_ == mode::unchanged && caching_ == caching::unchanged) + { + result<file_handle> ret(file_handle(native_handle_type(), _devid, _inode, caching_, _flags)); + ret.value()._service = _service; + ret.value()._v.behaviour = _v.behaviour; + if(!DuplicateHandle(GetCurrentProcess(), _v.h, GetCurrentProcess(), &ret.value()._v.h, 0, false, DUPLICATE_SAME_ACCESS)) + return {GetLastError(), std::system_category()}; + return ret; + } + // Slow path + windows_nt_kernel::init(); + using namespace windows_nt_kernel; + result<file_handle> ret(file_handle(native_handle_type(), _devid, _inode, caching_, _flags)); + native_handle_type &nativeh = ret.value()._v; + nativeh.behaviour |= native_handle_type::disposition::file; + DWORD fileshare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + OUTCOME_TRY(access, access_mask_from_handle_mode(nativeh, mode_, _flags)); + OUTCOME_TRYV(attributes_from_handle_caching_and_flags(nativeh, caching_, _flags)); + OUTCOME_TRY(ntflags, ntflags_from_handle_caching_and_flags(nativeh, caching_, _flags)); + ntflags |= 0x040 /*FILE_NON_DIRECTORY_FILE*/; // do not open a directory + OBJECT_ATTRIBUTES oa; + memset(&oa, 0, sizeof(oa)); + oa.Length = sizeof(OBJECT_ATTRIBUTES); + // It is entirely undocumented that this is how you clone a file handle with new privs + UNICODE_STRING _path; + memset(&_path, 0, sizeof(_path)); + oa.ObjectName = &_path; + oa.RootDirectory = _v.h; + IO_STATUS_BLOCK isb = make_iostatus(); + NTSTATUS ntstat = NtOpenFile(&nativeh.h, access, &oa, &isb, fileshare, ntflags); + if(STATUS_PENDING == ntstat) + ntstat = ntwait(nativeh.h, isb, deadline()); + if(ntstat < 0) + { + return {(int) ntstat, ntkernel_category()}; + } return ret; } diff --git a/include/afio/v2.0/detail/impl/windows/handle.ipp b/include/afio/v2.0/detail/impl/windows/handle.ipp index 21f4dcad..ceadb0d0 100644 --- a/include/afio/v2.0/detail/impl/windows/handle.ipp +++ b/include/afio/v2.0/detail/impl/windows/handle.ipp @@ -109,26 +109,4 @@ result<void> handle::set_append_only(bool enable) noexcept return success(); } -result<void> handle::set_kernel_caching(caching caching) noexcept -{ - AFIO_LOG_FUNCTION_CALL(this); - native_handle_type nativeh; - handle::mode _mode = mode::none; - if(is_append_only()) - _mode = mode::append; - else if(is_writable()) - _mode = mode::write; - else if(is_readable()) - _mode = mode::read; - OUTCOME_TRY(access, access_mask_from_handle_mode(nativeh, _mode, _flags)); - OUTCOME_TRY(attribs, attributes_from_handle_caching_and_flags(nativeh, caching, _flags)); - nativeh.behaviour |= native_handle_type::disposition::file; - if(INVALID_HANDLE_VALUE == (nativeh.h = ReOpenFile(_v.h, access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, attribs))) - return {GetLastError(), std::system_category()}; - _v.swap(nativeh); - if(!CloseHandle(nativeh.h)) - return {GetLastError(), std::system_category()}; - return success(); -} - AFIO_V2_NAMESPACE_END diff --git a/include/afio/v2.0/directory_handle.hpp b/include/afio/v2.0/directory_handle.hpp index 8710243a..d13cd2e6 100644 --- a/include/afio/v2.0/directory_handle.hpp +++ b/include/afio/v2.0/directory_handle.hpp @@ -218,11 +218,18 @@ public: return path_handle::close(); } - /*! Clone this handle (copy constructor is disabled to avoid accidental copying) + /*! Clone this handle (copy constructor is disabled to avoid accidental copying), + optionally race free reopening the handle with different access or caching. + + Microsoft Windows provides a syscall for cloning an existing handle but with new + access. On POSIX, we must loop calling `current_path()`, + trying to open the path returned and making sure it is the same inode. \errors Any of the values POSIX dup() or DuplicateHandle() can return. + \mallocs On POSIX if changing the mode, we must loop calling `current_path()` and + trying to open the path returned. Thus many allocations may occur. */ - AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<directory_handle> clone() const noexcept; + AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<directory_handle> clone(mode _mode = mode::unchanged, caching _caching = caching::unchanged, deadline d = std::chrono::seconds(30)) const noexcept; #ifdef _WIN32 AFIO_HEADERS_ONLY_VIRTUAL_SPEC diff --git a/include/afio/v2.0/file_handle.hpp b/include/afio/v2.0/file_handle.hpp index 75e9d6c7..03356a24 100644 --- a/include/afio/v2.0/file_handle.hpp +++ b/include/afio/v2.0/file_handle.hpp @@ -209,11 +209,19 @@ public: AFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> barrier(io_request<const_buffers_type> reqs = io_request<const_buffers_type>(), bool wait_for_device = false, bool and_metadata = false, deadline d = deadline()) noexcept override; - /*! Clone this handle (copy constructor is disabled to avoid accidental copying) + /*! Clone this handle (copy constructor is disabled to avoid accidental copying), + optionally race free reopening the handle with different access or caching. + + Microsoft Windows provides a syscall for cloning an existing handle but with new + access. On POSIX, if not changing the mode, we change caching via `fcntl()`, if + changing the mode we must loop calling `current_path()`, + trying to open the path returned and making sure it is the same inode. \errors Any of the values POSIX dup() or DuplicateHandle() can return. + \mallocs On POSIX if changing the mode, we must loop calling `current_path()` and + trying to open the path returned. Thus many allocations may occur. */ - AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<file_handle> clone() const noexcept; + AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<file_handle> clone(mode _mode = mode::unchanged, caching _caching = caching::unchanged, deadline d = std::chrono::seconds(30)) const noexcept; //! The i/o service this handle is attached to, if any io_service *service() const noexcept { return _service; } diff --git a/include/afio/v2.0/handle.hpp b/include/afio/v2.0/handle.hpp index 49d5a209..47762f6c 100644 --- a/include/afio/v2.0/handle.hpp +++ b/include/afio/v2.0/handle.hpp @@ -309,17 +309,6 @@ public: bool are_writes_durable() const noexcept { return _caching == caching::none || _caching == caching::reads || _caching == caching::reads_and_metadata; } //! True if issuing safety fsyncs is on bool are_safety_fsyncs_issued() const noexcept { return !(_flags & flag::disable_safety_fsyncs) && !!(static_cast<int>(_caching) & 1); } - /*! Changes the kernel cache strategy used by this handle. - Note most OSs impose severe restrictions on what can be changed and will error out, - it may be easier to simply create a new handle. - - \warning On Windows this reopens the file, it is no slower than - opening the file fresh but equally it is vastly slower than on POSIX. - - \errors Whatever POSIX fcntl() or ReOpenFile() returns. - \mallocs No memory allocation. - */ - AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> set_kernel_caching(caching caching) noexcept; //! The flags this handle was opened with flag flags() const noexcept { return _flags; } diff --git a/include/afio/v2.0/mapped_file_handle.hpp b/include/afio/v2.0/mapped_file_handle.hpp index 49fb304a..e9b1d3ef 100644 --- a/include/afio/v2.0/mapped_file_handle.hpp +++ b/include/afio/v2.0/mapped_file_handle.hpp @@ -356,10 +356,15 @@ public: { return _mh.barrier(std::move(reqs), wait_for_device, and_metadata, std::move(d)); } - using file_handle::clone; - inline result<mapped_file_handle> clone(size_type reservation) const noexcept + AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<file_handle> clone(mode _mode = mode::unchanged, caching _caching = caching::unchanged, deadline d = std::chrono::seconds(30)) const noexcept override { - OUTCOME_TRY(fh, clone()); + OUTCOME_TRY(fh, file_handle::clone(_mode, _caching, d)); + mapped_file_handle ret(std::move(fh), _reservation); + return static_cast<file_handle &&>(ret); + } + result<mapped_file_handle> clone(size_type reservation, mode _mode = mode::unchanged, caching _caching = caching::unchanged, deadline d = std::chrono::seconds(30)) const noexcept + { + OUTCOME_TRY(fh, file_handle::clone(_mode, _caching, d)); return mapped_file_handle(std::move(fh), reservation); } //! Return the current maximum permitted extent of the file which is the lesser of the section's length, or the reservation. diff --git a/test/tests/directory_handle_create_close/runner.cpp b/test/tests/directory_handle_create_close/runner.cpp index dee37228..1980c0cf 100644 --- a/test/tests/directory_handle_create_close/runner.cpp +++ b/test/tests/directory_handle_create_close/runner.cpp @@ -22,8 +22,8 @@ Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt) */ -#include "kerneltest/include/kerneltest.hpp" #include "kernel_directory_handle.cpp.hpp" +#include "kerneltest/include/kerneltest.hpp" template <class U> inline void directory_handle_create_close_creation(U &&f) { @@ -76,9 +76,6 @@ template <class U> inline void directory_handle_create_close_creation(U &&f) { is_a_directory, { directory_handle::mode::write, directory_handle::creation::truncate , directory_handle::flag::none, &entries, &info }, { "non-existing" }, { "non-existing" },{ success() } }, { is_a_directory, { directory_handle::mode::write, directory_handle::creation::truncate , directory_handle::flag::none, &entries, &info }, { "existing0" }, { "existing0" },{ success() } }, { is_a_directory, { directory_handle::mode::write, directory_handle::creation::truncate , directory_handle::flag::none, &entries, &info }, { "existing1" }, { "existing1" },{ success() } }, - - // Does the flag parameter have the expected side effects? - { success(), { directory_handle::mode::write, directory_handle::creation::open_existing, directory_handle::flag::unlink_on_close, &entries, &info }, { "existing1" }, { "existing1" },{ success() } } }, precondition::filesystem_setup(), postcondition::filesystem_comparison_structure(), |