diff options
31 files changed, 1456 insertions, 284 deletions
diff --git a/cmake/headers.cmake b/cmake/headers.cmake index 01211f26..53a1dfd0 100644 --- a/cmake/headers.cmake +++ b/cmake/headers.cmake @@ -38,6 +38,7 @@ set(llfio_HEADERS "include/llfio/v2.0/statfs.hpp" "include/llfio/v2.0/status_code.hpp" "include/llfio/v2.0/storage_profile.hpp" + "include/llfio/v2.0/symlink_handle.hpp" "include/llfio/v2.0/utils.hpp" "include/llfio/version.hpp" "include/llfio/ntkernel-error-category/include/detail/ntkernel-table.ipp" @@ -58,6 +59,7 @@ set(llfio_HEADERS "include/llfio/v2.0/detail/impl/posix/stat.ipp" "include/llfio/v2.0/detail/impl/posix/statfs.ipp" "include/llfio/v2.0/detail/impl/posix/storage_profile.ipp" + "include/llfio/v2.0/detail/impl/posix/symlink_handle.ipp" "include/llfio/v2.0/detail/impl/posix/utils.ipp" "include/llfio/v2.0/detail/impl/safe_byte_ranges.ipp" "include/llfio/v2.0/detail/impl/storage_profile.ipp" @@ -76,5 +78,6 @@ set(llfio_HEADERS "include/llfio/v2.0/detail/impl/windows/stat.ipp" "include/llfio/v2.0/detail/impl/windows/statfs.ipp" "include/llfio/v2.0/detail/impl/windows/storage_profile.ipp" + "include/llfio/v2.0/detail/impl/windows/symlink_handle.ipp" "include/llfio/v2.0/detail/impl/windows/utils.ipp" ) diff --git a/cmake/tests.cmake b/cmake/tests.cmake index a0b676b2..77a678c4 100644 --- a/cmake/tests.cmake +++ b/cmake/tests.cmake @@ -7,6 +7,7 @@ set(llfio_TESTS "test/tests/file_handle_create_close/kernel_file_handle.cpp.hpp" "test/tests/map_handle_create_close/kernel_map_handle.cpp.hpp" "test/tests/section_handle_create_close/kernel_section_handle.cpp.hpp" + "test/tests/symlink_handle_create_close/kernel_symlink_handle.cpp.hpp" "test/tests/async_io.cpp" "test/tests/coroutines.cpp" "test/tests/current_path.cpp" @@ -20,11 +21,13 @@ set(llfio_TESTS "test/tests/path_view.cpp" "test/tests/section_handle_create_close/runner.cpp" "test/tests/shared_fs_mutex.cpp" + "test/tests/symlink_handle_create_close/runner.cpp" "test/tests/trivial_vector.cpp" ) # DO NOT EDIT, GENERATED BY SCRIPT set(llfio_COMPILE_TESTS "example/single-header.cpp" + "example/ts_examples.cpp" "example/use_cases.cpp" ) # DO NOT EDIT, GENERATED BY SCRIPT diff --git a/example/ts_examples.cpp b/example/ts_examples.cpp new file mode 100644 index 00000000..16c7c931 --- /dev/null +++ b/example/ts_examples.cpp @@ -0,0 +1,103 @@ +/* Examples from the P1031 Low level file i/o Technical Specification +(C) 2018 Niall Douglas <http://www.nedproductions.biz/> (2 commits) +File Created: Aug 2018 + + +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 "../include/llfio.hpp" + +using namespace LLFIO_V2_NAMESPACE; +using namespace std; + +#define throws(x) +#define VALUE .value() + +inline io_handle::buffers_type read_all(io_handle &h, io_handle::io_request<io_handle::buffers_type> reqs, deadline d = deadline()) throws(file_io_error) +{ + // Record beginning if deadline is specified + chrono::steady_clock::time_point began_steady; + if(d && d.steady) + began_steady = chrono::steady_clock::now(); + + // Take copy of input buffers onto stack, and set output buffers to buffers supplied + auto *input_buffers_mem = reinterpret_cast<io_handle::buffer_type *>(alloca(reqs.buffers.size() * sizeof(io_handle::buffer_type))); + auto *input_buffers_sizes = reinterpret_cast<io_handle::extent_type *>(alloca(reqs.buffers.size() * sizeof(io_handle::extent_type))); + io_handle::buffers_type output_buffers(reqs.buffers); + io_handle::io_request<io_handle::buffers_type> creq({input_buffers_mem, reqs.buffers.size()}, 0); + for(size_t n = 0; n < reqs.buffers.size(); n++) + { + // Copy input buffer to stack and retain original size + creq.buffers[n] = reqs.buffers[n]; + input_buffers_sizes[n] = reqs.buffers[n].size(); + // Set output buffer length to zero + output_buffers[n] = io_handle::buffer_type{output_buffers[n].data(), 0}; + } + + // Track which output buffer we are currently filling + size_t idx = 0; + do + { + // New deadline for this loop + deadline nd; + if(d) + { + if(d.steady) + { + auto ns = chrono::duration_cast<chrono::nanoseconds>((began_steady + chrono::nanoseconds(d.nsecs)) - chrono::steady_clock::now()); + if(ns.count() < 0) + nd.nsecs = 0; + else + nd.nsecs = ns.count(); + } + else + nd = d; + } + // Partial fill buffers with current request + io_handle::buffers_type filled = h.read(creq, nd) VALUE; + + // Adjust output buffers by what was filled, and prepare input + // buffers for next round of partial fill + for(size_t n = 0; n < creq.buffers.size(); n++) + { + // Add the amount of this buffer filled to next offset read and to output buffer + auto &input_buffer = creq.buffers[n]; + auto &output_buffer = output_buffers[idx + n]; + creq.offset += input_buffer.size(); + output_buffer = io_handle::buffer_type{output_buffer.data(), output_buffer.size() + input_buffer.size()}; + // Adjust input buffer to amount remaining + input_buffer = io_handle::buffer_type{input_buffer.data() + input_buffer.size(), input_buffers_sizes[idx + n] - output_buffer.size()}; + } + + // Remove completely filled input buffers + while(!creq.buffers.empty() && creq.buffers[0].size() == 0) + { + creq.buffers = io_handle::buffers_type(creq.buffers.data() + 1, creq.buffers.size() - 1); + ++idx; + } + } while(!creq.buffers.empty()); + return output_buffers; +} + + +int main() +{ + return 0; +}
\ No newline at end of file diff --git a/include/llfio/revision.hpp b/include/llfio/revision.hpp index 5470bae0..dfdb3db0 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 2b1b67be8b3f2bd0bf563fee070c12b95c3d1460 -#define LLFIO_PREVIOUS_COMMIT_DATE "2018-07-12 17:59:46 +00:00" -#define LLFIO_PREVIOUS_COMMIT_UNIQUE 2b1b67be +#define LLFIO_PREVIOUS_COMMIT_REF 084b3eb8aaae639e9a63e97245386fd78b8d8413 +#define LLFIO_PREVIOUS_COMMIT_DATE "2018-07-13 19:20:14 +00:00" +#define LLFIO_PREVIOUS_COMMIT_UNIQUE 084b3eb8 diff --git a/include/llfio/v2.0/algorithm/shared_fs_mutex/atomic_append.hpp b/include/llfio/v2.0/algorithm/shared_fs_mutex/atomic_append.hpp index b285cef5..39d08564 100644 --- a/include/llfio/v2.0/algorithm/shared_fs_mutex/atomic_append.hpp +++ b/include/llfio/v2.0/algorithm/shared_fs_mutex/atomic_append.hpp @@ -139,9 +139,9 @@ namespace algorithm do { OUTCOME_TRY(_, _h.read(0, {{reinterpret_cast<byte *>(&_header), 48}})); - if(_[0].data != reinterpret_cast<byte *>(&_header)) + if(_[0].data() != reinterpret_cast<byte *>(&_header)) { - memcpy(&_header, _[0].data, _[0].len); + memcpy(&_header, _[0].data(), _[0].size()); } if(_skip_hashing) { @@ -307,7 +307,8 @@ namespace algorithm std::terminate(); } const atomic_append_detail::lock_request *record, *lastrecord; - for(record = reinterpret_cast<const atomic_append_detail::lock_request *>(readoutcome.value()[0].data), lastrecord = reinterpret_cast<const atomic_append_detail::lock_request *>(readoutcome.value()[0].data + readoutcome.value()[0].len); record < lastrecord && record->hash != lock_request.hash; ++record) + for(record = reinterpret_cast<const atomic_append_detail::lock_request *>(readoutcome.value()[0].data()), lastrecord = reinterpret_cast<const atomic_append_detail::lock_request *>(readoutcome.value()[0].data() + readoutcome.value()[0].size()); record < lastrecord && record->hash != lock_request.hash; + ++record) { my_lock_request_offset += sizeof(atomic_append_detail::lock_request); } @@ -361,9 +362,9 @@ namespace algorithm assert(record_offset >= start_offset); assert(record_offset - start_offset <= sizeof(_buffer)); OUTCOME_TRY(batchread, _h.read(start_offset, {{_buffer, (size_t)(record_offset - start_offset) + sizeof(atomic_append_detail::lock_request)}})); - assert(batchread[0].len == record_offset - start_offset + sizeof(atomic_append_detail::lock_request)); - const atomic_append_detail::lock_request *record = reinterpret_cast<atomic_append_detail::lock_request *>(batchread[0].data + batchread[0].len - sizeof(atomic_append_detail::lock_request)); - const atomic_append_detail::lock_request *firstrecord = reinterpret_cast<atomic_append_detail::lock_request *>(batchread[0].data); + assert(batchread[0].size() == record_offset - start_offset + sizeof(atomic_append_detail::lock_request)); + const atomic_append_detail::lock_request *record = reinterpret_cast<atomic_append_detail::lock_request *>(batchread[0].data() + batchread[0].size() - sizeof(atomic_append_detail::lock_request)); + const atomic_append_detail::lock_request *firstrecord = reinterpret_cast<atomic_append_detail::lock_request *>(batchread[0].data()); // Skip all completed lock requests or not mentioning any of my entities for(; record >= firstrecord; record_offset -= sizeof(atomic_append_detail::lock_request), --record) @@ -510,12 +511,12 @@ namespace algorithm } const auto &bytesread = bytesread_.value(); // If read was partial, we are done after this round - if(bytesread[0].len < sizeof(_buffer)) + if(bytesread[0].size() < sizeof(_buffer)) { done = true; } - const auto *record = reinterpret_cast<const atomic_append_detail::lock_request *>(bytesread[0].data); - const auto *lastrecord = reinterpret_cast<const atomic_append_detail::lock_request *>(bytesread[0].data + bytesread[0].len); + const auto *record = reinterpret_cast<const atomic_append_detail::lock_request *>(bytesread[0].data()); + const auto *lastrecord = reinterpret_cast<const atomic_append_detail::lock_request *>(bytesread[0].data() + bytesread[0].size()); for(; record < lastrecord; ++record) { if(!record->hash && (record->unique_id == 0u)) diff --git a/include/llfio/v2.0/config.hpp b/include/llfio/v2.0/config.hpp index f105f3b6..d1280639 100644 --- a/include/llfio/v2.0/config.hpp +++ b/include/llfio/v2.0/config.hpp @@ -27,7 +27,7 @@ Distributed under the Boost Software License, Version 1.0. //#include <iostream> //#define LLFIO_LOG_TO_OSTREAM std::cerr -//#define LLFIO_LOGGING_LEVEL 6 +#define LLFIO_LOGGING_LEVEL 1 //#define LLFIO_DISABLE_PATHS_IN_FAILURE_INFO //! \file config.hpp Configures a compiler environment for LLFIO header and source code diff --git a/include/llfio/v2.0/detail/impl/posix/async_file_handle.ipp b/include/llfio/v2.0/detail/impl/posix/async_file_handle.ipp index b24dff98..4a3a8652 100644 --- a/include/llfio/v2.0/detail/impl/posix/async_file_handle.ipp +++ b/include/llfio/v2.0/detail/impl/posix/async_file_handle.ipp @@ -111,7 +111,7 @@ template <class BuffersType, class IORoutine> result<async_file_handle::io_state LLFIO_LOG_FATAL(0, "file_handle::io_state::operator() called with invalid index"); std::terminate(); } - result.value()[idx].len = bytes_transferred; + result.value()[idx] = {result.value()[idx].data(), (size_type) bytes_transferred}; } } this->parent->service()->_work_done(); @@ -228,15 +228,15 @@ template <class BuffersType, class IORoutine> result<async_file_handle::io_state if(_v.requires_aligned_io()) { assert((offset & 511) == 0); - assert(((uintptr_t) out[n].data & 511) == 0); - assert((out[n].len & 511) == 0); + assert(((uintptr_t) out[n].data() & 511) == 0); + assert((out[n].size() & 511) == 0); } #endif struct aiocb *aiocb = state->aiocbs + n; aiocb->aio_fildes = _v.fd; aiocb->aio_offset = offset; - aiocb->aio_buf = reinterpret_cast<void *>(const_cast<byte *>(out[n].data)); - aiocb->aio_nbytes = out[n].len; + aiocb->aio_buf = reinterpret_cast<void *>(const_cast<byte *>(out[n].data())); + aiocb->aio_nbytes = out[n].size(); aiocb->aio_sigevent.sigev_notify = SIGEV_NONE; aiocb->aio_sigevent.sigev_value.sival_ptr = reinterpret_cast<void *>(state); switch(operation) @@ -259,7 +259,7 @@ template <class BuffersType, class IORoutine> result<async_file_handle::io_state #else #error todo #endif - offset += out[n].len; + offset += out[n].size(); ++state->items_to_go; } int ret = 0; diff --git a/include/llfio/v2.0/detail/impl/posix/file_handle.ipp b/include/llfio/v2.0/detail/impl/posix/file_handle.ipp index 3ae2dce0..7972f16b 100644 --- a/include/llfio/v2.0/detail/impl/posix/file_handle.ipp +++ b/include/llfio/v2.0/detail/impl/posix/file_handle.ipp @@ -145,7 +145,7 @@ file_handle::io_result<file_handle::const_buffers_type> file_handle::barrier(fil // empty buffers means bytes = 0 which means sync entire file for(const auto &req : reqs.buffers) { - bytes += req.len; + bytes += req.size(); } unsigned flags = SYNC_FILE_RANGE_WRITE; // start writing all dirty pages in range now if(wait_for_device) @@ -464,7 +464,7 @@ result<file_handle::extent_type> file_handle::zero(file_handle::extent_type offs auto *buffer = static_cast<byte *>(alloca(bytes)); memset(buffer, 0, bytes); OUTCOME_TRY(written, write(offset, {{buffer, bytes}}, d)); - return written[0].len; + return written[0].size(); } try { @@ -477,9 +477,9 @@ result<file_handle::extent_type> file_handle::zero(file_handle::extent_type offs { auto towrite = (bytes < blocksize) ? bytes : blocksize; OUTCOME_TRY(written, write(offset, {{buffer, towrite}}, d)); - offset += written[0].len; - bytes -= written[0].len; - ret += written[0].len; + offset += written[0].size(); + bytes -= written[0].size(); + ret += written[0].size(); } return ret; } diff --git a/include/llfio/v2.0/detail/impl/posix/io_handle.ipp b/include/llfio/v2.0/detail/impl/posix/io_handle.ipp index 1b67de56..d2e5b82a 100644 --- a/include/llfio/v2.0/detail/impl/posix/io_handle.ipp +++ b/include/llfio/v2.0/detail/impl/posix/io_handle.ipp @@ -34,6 +34,13 @@ Distributed under the Boost Software License, Version 1.0. LLFIO_V2_NAMESPACE_BEGIN +constexpr inline void _check_iovec_match() +{ + static_assert(sizeof(io_handle::buffer_type) == sizeof(iovec), "buffer_type and struct iovec do not match in size"); + static_assert(offsetof(io_handle::buffer_type, _data) == offsetof(iovec, iov_base), "buffer_type and struct iovec do not have same offset of data member"); + static_assert(offsetof(io_handle::buffer_type, _len) == offsetof(iovec, iov_len), "buffer_type and struct iovec do not have same offset of len member"); +} + size_t io_handle::max_buffers() const noexcept { static size_t v; @@ -76,7 +83,6 @@ io_handle::io_result<io_handle::buffers_type> io_handle::read(io_handle::io_requ iov[n].iov_len = reqs.buffers[n].len; } #else - static_assert(sizeof(buffer_type) == sizeof(iovec), "buffer_type and struct iovec do not match"); auto *iov = reinterpret_cast<struct iovec *>(reqs.buffers.data()); #endif #ifndef NDEBUG @@ -107,18 +113,18 @@ io_handle::io_result<io_handle::buffers_type> io_handle::read(io_handle::io_requ } for(auto &buffer : reqs.buffers) { - if(buffer.len >= static_cast<size_t>(bytesread)) + if(buffer.size() >= static_cast<size_t>(bytesread)) { - bytesread -= buffer.len; + bytesread -= buffer.size(); } else if(bytesread > 0) { - buffer.len = bytesread; + buffer = {buffer.data(), (size_type) bytesread}; bytesread = 0; } else { - buffer.len = 0; + buffer = {buffer.data(), 0}; } } return {reqs.buffers}; @@ -143,7 +149,6 @@ io_handle::io_result<io_handle::const_buffers_type> io_handle::write(io_handle:: iov[n].iov_len = reqs.buffers[n].len; } #else - static_assert(sizeof(buffer_type) == sizeof(iovec), "buffer_type and struct iovec do not match"); auto *iov = reinterpret_cast<struct iovec *>(reqs.buffers.data()); #endif #ifndef NDEBUG @@ -174,18 +179,18 @@ io_handle::io_result<io_handle::const_buffers_type> io_handle::write(io_handle:: } for(auto &buffer : reqs.buffers) { - if(buffer.len >= static_cast<size_t>(byteswritten)) + if(buffer.size() >= static_cast<size_t>(byteswritten)) { - byteswritten -= buffer.len; + byteswritten -= buffer.size(); } else if(byteswritten > 0) { - buffer.len = byteswritten; + buffer = {buffer.data(), (size_type) byteswritten}; byteswritten = 0; } else { - buffer.len = 0; + buffer = {buffer.data(), 0}; } } return {reqs.buffers}; @@ -257,8 +262,6 @@ result<io_handle::extent_guard> io_handle::lock(io_handle::extent_type offset, i { return errc::timed_out; } - - return posix_error(); } return extent_guard(this, offset, bytes, exclusive); diff --git a/include/llfio/v2.0/detail/impl/posix/map_handle.ipp b/include/llfio/v2.0/detail/impl/posix/map_handle.ipp index 8531033d..60e4ec14 100644 --- a/include/llfio/v2.0/detail/impl/posix/map_handle.ipp +++ b/include/llfio/v2.0/detail/impl/posix/map_handle.ipp @@ -189,11 +189,11 @@ map_handle::io_result<map_handle::const_buffers_type> map_handle::barrier(map_ha // Check for overflow for(const auto &req : reqs.buffers) { - if(bytes + req.len < bytes) + if(bytes + req.size() < bytes) { return errc::value_too_large; } - bytes += req.len; + bytes += req.size(); } // If empty, do the whole file if(reqs.buffers.empty()) @@ -204,7 +204,7 @@ map_handle::io_result<map_handle::const_buffers_type> map_handle::barrier(map_ha if(!and_metadata && is_nvram()) { auto synced = barrier({addr, bytes}); - if(synced.len >= bytes) + if(synced.size() >= bytes) { return {reqs.buffers}; } @@ -426,17 +426,17 @@ result<map_handle::size_type> map_handle::truncate(size_type newsize, bool permi result<map_handle::buffer_type> map_handle::commit(buffer_type region, section_handle::flag flag) noexcept { LLFIO_LOG_FUNCTION_CALL(this); - if(region.data == nullptr) + if(region.data() == nullptr) { return errc::invalid_argument; } // Set permissions on the pages region = utils::round_to_page_size(region); - extent_type offset = _offset + (region.data - _addr); - size_type bytes = region.len; - OUTCOME_TRYV(do_mmap(_v, region.data, MAP_FIXED, _section, bytes, offset, flag)); + extent_type offset = _offset + (region.data() - _addr); + size_type bytes = region.size(); + OUTCOME_TRYV(do_mmap(_v, region.data(), MAP_FIXED, _section, bytes, offset, flag)); // Tell the kernel we will be using these pages soon - if(-1 == ::madvise(region.data, region.len, MADV_WILLNEED)) + if(-1 == ::madvise(region.data(), region.size(), MADV_WILLNEED)) { return posix_error(); } @@ -446,51 +446,51 @@ result<map_handle::buffer_type> map_handle::commit(buffer_type region, section_h result<map_handle::buffer_type> map_handle::decommit(buffer_type region) noexcept { LLFIO_LOG_FUNCTION_CALL(this); - if(region.data == nullptr) + if(region.data() == nullptr) { return errc::invalid_argument; } region = utils::round_to_page_size(region); // Tell the kernel to kick these pages into storage - if(-1 == ::madvise(region.data, region.len, MADV_DONTNEED)) + if(-1 == ::madvise(region.data(), region.size(), MADV_DONTNEED)) { return posix_error(); } // Set permissions on the pages to no access - extent_type offset = _offset + (region.data - _addr); - size_type bytes = region.len; - OUTCOME_TRYV(do_mmap(_v, region.data, MAP_FIXED, _section, bytes, offset, section_handle::flag::none)); + extent_type offset = _offset + (region.data() - _addr); + size_type bytes = region.size(); + OUTCOME_TRYV(do_mmap(_v, region.data(), MAP_FIXED, _section, bytes, offset, section_handle::flag::none)); return region; } result<void> map_handle::zero_memory(buffer_type region) noexcept { LLFIO_LOG_FUNCTION_CALL(this); - if(region.data == nullptr) + if(region.data() == nullptr) { return errc::invalid_argument; } #ifdef MADV_REMOVE - buffer_type page_region{utils::round_up_to_page_size(region.data), utils::round_down_to_page_size(region.len)}; + buffer_type page_region{utils::round_up_to_page_size(region.data()), utils::round_down_to_page_size(region.size())}; // Zero contents and punch a hole in any backing storage - if((page_region.len != 0u) && -1 != ::madvise(page_region.data, page_region.len, MADV_REMOVE)) + if((page_region.size() != 0u) && -1 != ::madvise(page_region.data(), page_region.size(), MADV_REMOVE)) { - memset(region.data, 0, page_region.data - region.data); - memset(page_region.data + page_region.len, 0, (region.data + region.len) - (page_region.data + page_region.len)); + memset(region.data(), 0, page_region.data() - region.data()); + memset(page_region.data() + page_region.size(), 0, (region.data() + region.size()) - (page_region.data() + page_region.size())); return success(); } #endif //! Only Linux implements syscall zero(), and it's covered by MADV_REMOVE already - memset(region.data, 0, region.len); + memset(region.data(), 0, region.size()); return success(); } result<span<map_handle::buffer_type>> map_handle::prefetch(span<buffer_type> regions) noexcept { LLFIO_LOG_FUNCTION_CALL(0); - for(const auto ®ion : regions) + for(auto ®ion : regions) { - if(-1 == ::madvise(region.data, region.len, MADV_WILLNEED)) + if(-1 == ::madvise(region.data(), region.size(), MADV_WILLNEED)) { return posix_error(); } @@ -502,25 +502,25 @@ result<map_handle::buffer_type> map_handle::do_not_store(buffer_type region) noe { LLFIO_LOG_FUNCTION_CALL(0); region = utils::round_to_page_size(region); - if(region.data == nullptr) + if(region.data() == nullptr) { return errc::invalid_argument; } #ifdef MADV_FREE // Lightweight unset of dirty bit for these pages. Needs FreeBSD or very recent Linux. - if(-1 != ::madvise(region.data, region.len, MADV_FREE)) + if(-1 != ::madvise(region.data(), region.size(), MADV_FREE)) return region; #endif #ifdef MADV_REMOVE // This is rather heavy weight in that it also punches a hole in any backing storage // but it works on Linux for donkey's years - if(-1 != ::madvise(region.data, region.len, MADV_REMOVE)) + if(-1 != ::madvise(region.data(), region.size(), MADV_REMOVE)) { return region; } #endif // No support on this platform - region.len = 0; + region = {region.data(), 0}; return region; } @@ -533,17 +533,17 @@ map_handle::io_result<map_handle::buffers_type> map_handle::read(io_request<buff { if(togo != 0u) { - req.data = addr; - if(req.len > togo) + req = {addr, req.size()}; + if(req.size() > togo) { - req.len = togo; + req = {req.data(), togo}; } - addr += req.len; - togo -= req.len; + addr += req.size(); + togo -= req.size(); } else { - req.len = 0; + req = {req.data(), 0}; } } return reqs.buffers; @@ -560,18 +560,18 @@ map_handle::io_result<map_handle::const_buffers_type> map_handle::write(io_reque { if(togo != 0u) { - if(req.len > togo) + if(req.size() > togo) { - req.len = togo; + req = {req.data(), togo}; } - memcpy(addr, req.data, req.len); - req.data = addr; - addr += req.len; - togo -= req.len; + memcpy(addr, req.data(), req.size()); + req = {addr, req.size()}; + addr += req.size(); + togo -= req.size(); } else { - req.len = 0; + req = {req.data(), 0}; } } return false; diff --git a/include/llfio/v2.0/detail/impl/windows/async_file_handle.ipp b/include/llfio/v2.0/detail/impl/windows/async_file_handle.ipp index 927de39a..4efb577b 100644 --- a/include/llfio/v2.0/detail/impl/windows/async_file_handle.ipp +++ b/include/llfio/v2.0/detail/impl/windows/async_file_handle.ipp @@ -70,7 +70,7 @@ template <class BuffersType, class IORoutine> result<async_file_handle::io_state LLFIO_LOG_FATAL(0, "async_file_handle::io_state::operator() called with invalid index"); std::terminate(); } - result.value()[idx].len = bytes_transferred; + result.value()[idx] = {result.value()[idx].data(), (size_t) bytes_transferred}; } } this->parent->service()->_work_done(); @@ -177,16 +177,16 @@ template <class BuffersType, class IORoutine> result<async_file_handle::io_state } // Use the unused hEvent member to pass through the state ol->hEvent = reinterpret_cast<HANDLE>(state); - offset += out[n].len; + offset += out[n].size(); ++state->items_to_go; #ifndef NDEBUG if(_v.requires_aligned_io()) { - assert((reinterpret_cast<uintptr_t>(out[n].data) & 511) == 0); - assert((out[n].len & 511) == 0); + assert((reinterpret_cast<uintptr_t>(out[n].data()) & 511) == 0); + assert((out[n].size() & 511) == 0); } #endif - if(!ioroutine(_v.h, const_cast<byte *>(out[n].data), static_cast<DWORD>(out[n].len), ol, handle_completion::Do)) + if(!ioroutine(_v.h, const_cast<byte *>(out[n].data()), static_cast<DWORD>(out[n].size()), ol, handle_completion::Do)) { --state->items_to_go; state->result.write = win32_error(); diff --git a/include/llfio/v2.0/detail/impl/windows/directory_handle.ipp b/include/llfio/v2.0/detail/impl/windows/directory_handle.ipp index abdd5a1e..158bbc7f 100644 --- a/include/llfio/v2.0/detail/impl/windows/directory_handle.ipp +++ b/include/llfio/v2.0/detail/impl/windows/directory_handle.ipp @@ -138,50 +138,8 @@ result<directory_handle> directory_handle::directory(const path_handle &base, pa result<directory_handle> directory_handle::clone(mode mode_, caching caching_, deadline /* unused */) const noexcept { LLFIO_LOG_FUNCTION_CALL(this); - // 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, 0, DUPLICATE_SAME_ACCESS) == 0) - { - return win32_error(); - } - 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 ntkernel_error(ntstat); - } + result<directory_handle> ret(directory_handle(native_handle_type(), _devid, _inode, _caching, _flags)); + OUTCOME_TRY(do_clone_handle(ret.value()._v, _v, mode_, caching_, _flags, true)); return ret; } diff --git a/include/llfio/v2.0/detail/impl/windows/file_handle.ipp b/include/llfio/v2.0/detail/impl/windows/file_handle.ipp index 885bd39c..82c855ae 100644 --- a/include/llfio/v2.0/detail/impl/windows/file_handle.ipp +++ b/include/llfio/v2.0/detail/impl/windows/file_handle.ipp @@ -163,19 +163,6 @@ result<file_handle> file_handle::file(const path_handle &base, file_handle::path #endif } } - if(flags & flag::unlink_on_first_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()); - } - } if(_creation == creation::truncate && ret.value().are_safety_fsyncs_issued()) { FlushFileBuffers(nativeh.h); @@ -342,47 +329,9 @@ file_handle::io_result<file_handle::const_buffers_type> file_handle::barrier(fil result<file_handle> file_handle::clone(mode mode_, caching caching_, deadline /*unused*/) const noexcept { LLFIO_LOG_FUNCTION_CALL(this); - // 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, 0, DUPLICATE_SAME_ACCESS) == 0) - { - return win32_error(); - } - 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 ntkernel_error(ntstat); - } + ret.value()._service = _service; + OUTCOME_TRY(do_clone_handle(ret.value()._v, _v, mode_, caching_, _flags)); return ret; } 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 0060d1d1..2b440326 100644 --- a/include/llfio/v2.0/detail/impl/windows/fs_handle.ipp +++ b/include/llfio/v2.0/detail/impl/windows/fs_handle.ipp @@ -200,7 +200,10 @@ result<void> fs_handle::unlink(deadline d) noexcept oa.ObjectName = &_path; oa.RootDirectory = h.native_handle().h; IO_STATUS_BLOCK isb = make_iostatus(); - NTSTATUS ntstat = NtOpenFile(&duph, SYNCHRONIZE | DELETE, &oa, &isb, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0x20 /*FILE_SYNCHRONOUS_IO_NONALERT*/); + DWORD ntflags = 0x20 /*FILE_SYNCHRONOUS_IO_NONALERT*/; + if(h.is_symlink()) + ntflags |= 0x00200000 /*FILE_OPEN_REPARSE_POINT*/; + NTSTATUS ntstat = NtOpenFile(&duph, SYNCHRONIZE | DELETE, &oa, &isb, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, ntflags); if(ntstat < 0) { return ntkernel_error(ntstat); diff --git a/include/llfio/v2.0/detail/impl/windows/import.hpp b/include/llfio/v2.0/detail/impl/windows/import.hpp index 20da757a..0c739dbd 100644 --- a/include/llfio/v2.0/detail/impl/windows/import.hpp +++ b/include/llfio/v2.0/detail/impl/windows/import.hpp @@ -958,7 +958,7 @@ inline HANDLE get_thread_local_waitable_timer() - sleep_interval: Set to the number of steady milliseconds until the sleep must end - sleep_object: Set to a primed deadline timer HANDLE which will signal when the system clock reaches the deadline */ -#define LLFIO_WIN_DEADLINE_TO_SLEEP_INIT(d) \ +#define LLFIO_WIN_DEADLINE_TO_SLEEP_INIT(d) \ std::chrono::steady_clock::time_point began_steady; \ \ std::chrono::system_clock::time_point end_utc; \ @@ -977,7 +977,7 @@ DWORD sleep_interval = INFINITE; \ HANDLE sleep_object = nullptr; -#define LLFIO_WIN_DEADLINE_TO_SLEEP_LOOP(d) \ +#define LLFIO_WIN_DEADLINE_TO_SLEEP_LOOP(d) \ \ if(d) \ \ @@ -1001,7 +1001,7 @@ if(d) \ } -#define LLFIO_WIN_DEADLINE_TO_TIMEOUT(type, d) \ +#define LLFIO_WIN_DEADLINE_TO_TIMEOUT(type, d) \ \ if(d) \ \ @@ -1025,7 +1025,7 @@ if(d) - sleep_interval: Set to the number of steady milliseconds until the sleep must end - sleep_object: Set to a primed deadline timer HANDLE which will signal when the system clock reaches the deadline */ -#define LLFIO_WIN_DEADLINE_TO_SLEEP_INIT(d) \ +#define LLFIO_WIN_DEADLINE_TO_SLEEP_INIT(d) \ std::chrono::steady_clock::time_point began_steady; \ \ std::chrono::system_clock::time_point end_utc; \ @@ -1050,7 +1050,7 @@ if(d) \ } -#define LLFIO_WIN_DEADLINE_TO_SLEEP_LOOP(d) \ +#define LLFIO_WIN_DEADLINE_TO_SLEEP_LOOP(d) \ if((d) && (d).steady) \ { \ std::chrono::nanoseconds ns = std::chrono::duration_cast<std::chrono::nanoseconds>((began_steady + std::chrono::nanoseconds((d).nsecs)) - std::chrono::steady_clock::now()); \ @@ -1060,7 +1060,7 @@ if(d) _timeout.QuadPart = ns.count() / -100; \ } -#define LLFIO_WIN_DEADLINE_TO_PARTIAL_DEADLINE(nd, d) \ +#define LLFIO_WIN_DEADLINE_TO_PARTIAL_DEADLINE(nd, d) \ if(d) \ { \ if((d).steady) \ @@ -1075,7 +1075,7 @@ if(d) (nd) = (d); \ } -#define LLFIO_WIN_DEADLINE_TO_TIMEOUT(d) \ +#define LLFIO_WIN_DEADLINE_TO_TIMEOUT(d) \ \ if(d) \ \ @@ -1296,6 +1296,55 @@ inline result<DWORD> ntflags_from_handle_caching_and_flags(native_handle_type &n return ntflags; } +inline result<void> do_clone_handle(native_handle_type &dest, const native_handle_type &src, handle::mode mode_, handle::caching caching_, handle::flag _flags, bool isdir = false) noexcept +{ + // Fast path + if(mode_ == handle::mode::unchanged && caching_ == handle::caching::unchanged) + { + dest.behaviour = src.behaviour; + if(DuplicateHandle(GetCurrentProcess(), dest.h, GetCurrentProcess(), const_cast<HANDLE *>(&src.h), 0, 0, DUPLICATE_SAME_ACCESS) == 0) + { + return win32_error(); + } + return success(); + } + // Slow path + windows_nt_kernel::init(); + using namespace windows_nt_kernel; + dest.behaviour |= src.behaviour & ~127U; // propagate type of handle only + DWORD fileshare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + OUTCOME_TRY(access, access_mask_from_handle_mode(dest, mode_, _flags)); + OUTCOME_TRYV(attributes_from_handle_caching_and_flags(dest, caching_, _flags)); + if(isdir) + { + /* 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(dest, 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 = src.h; + IO_STATUS_BLOCK isb = make_iostatus(); + NTSTATUS ntstat = NtOpenFile(&dest.h, access, &oa, &isb, fileshare, ntflags); + if(STATUS_PENDING == ntstat) + { + ntstat = ntwait(dest.h, isb, deadline()); + } + if(ntstat < 0) + { + return ntkernel_error(ntstat); + } + return success(); +} + /* Our own custom CreateFileW() implementation. The Win32 CreateFileW() implementation is unfortunately slow. It also, very annoyingly, diff --git a/include/llfio/v2.0/detail/impl/windows/io_handle.ipp b/include/llfio/v2.0/detail/impl/windows/io_handle.ipp index 239e917e..1c61ea2d 100644 --- a/include/llfio/v2.0/detail/impl/windows/io_handle.ipp +++ b/include/llfio/v2.0/detail/impl/windows/io_handle.ipp @@ -84,15 +84,15 @@ template <class BuffersType, class Syscall> inline io_handle::io_result<BuffersT #ifndef NDEBUG if(nativeh.requires_aligned_io()) { - assert(((uintptr_t) req.data & 511) == 0); - assert((req.len & 511) == 0); + assert(((uintptr_t) req.data() & 511) == 0); + assert((req.size() & 511) == 0); } #endif - if(!syscall(nativeh.h, req.data, static_cast<DWORD>(req.len), &transferred, &ol) && ERROR_IO_PENDING != GetLastError()) + if(!syscall(nativeh.h, req.data(), static_cast<DWORD>(req.size()), &transferred, &ol) && ERROR_IO_PENDING != GetLastError()) { return win32_error(); } - reqs.offset += req.len; + reqs.offset += req.size(); } // If handle is overlapped, wait for completion of each i/o. if(nativeh.is_overlapped()) @@ -116,7 +116,7 @@ template <class BuffersType, class Syscall> inline io_handle::io_result<BuffersT { return ntkernel_error(static_cast<NTSTATUS>(ols[n].Internal)); } - reqs.buffers[n].len = ols[n].InternalHigh; + reqs.buffers[n] = {reqs.buffers[n].data(), ols[n].InternalHigh}; } return io_handle::io_result<BuffersType>(std::move(reqs.buffers)); } diff --git a/include/llfio/v2.0/detail/impl/windows/map_handle.ipp b/include/llfio/v2.0/detail/impl/windows/map_handle.ipp index f650976f..ca54e008 100644 --- a/include/llfio/v2.0/detail/impl/windows/map_handle.ipp +++ b/include/llfio/v2.0/detail/impl/windows/map_handle.ipp @@ -441,11 +441,11 @@ map_handle::io_result<map_handle::const_buffers_type> map_handle::barrier(map_ha // Check for overflow for(const auto &req : reqs.buffers) { - if(bytes + req.len < bytes) + if(bytes + req.size() < bytes) { return errc::value_too_large; } - bytes += req.len; + bytes += req.size(); } // bytes = 0 means flush entire mapping if(bytes == 0) @@ -456,7 +456,7 @@ map_handle::io_result<map_handle::const_buffers_type> map_handle::barrier(map_ha if(!and_metadata && is_nvram()) { auto synced = barrier({addr, bytes}); - if(synced.len >= bytes) + if(synced.size() >= bytes) { return {reqs.buffers}; } @@ -646,14 +646,14 @@ result<map_handle::size_type> map_handle::truncate(size_type newsize, bool /* un result<map_handle::buffer_type> map_handle::commit(buffer_type region, section_handle::flag flag) noexcept { LLFIO_LOG_FUNCTION_CALL(this); - if(region.data == nullptr) + if(region.data() == nullptr) { return errc::invalid_argument; } DWORD prot = 0; if(flag == section_handle::flag::none) { - OUTCOME_TRYV(win32_maps_apply(region.data, region.len, [](byte *addr, size_t bytes) -> result<void> { + OUTCOME_TRYV(win32_maps_apply(region.data(), region.size(), [](byte *addr, size_t bytes) -> result<void> { DWORD _ = 0; if(VirtualProtect(addr, bytes, PAGE_NOACCESS, &_) == 0) { @@ -680,7 +680,7 @@ result<map_handle::buffer_type> map_handle::commit(buffer_type region, section_h prot = PAGE_EXECUTE; } region = utils::round_to_page_size(region); - OUTCOME_TRYV(win32_maps_apply(region.data, region.len, [prot](byte *addr, size_t bytes) -> result<void> { + OUTCOME_TRYV(win32_maps_apply(region.data(), region.size(), [prot](byte *addr, size_t bytes) -> result<void> { if(VirtualAlloc(addr, bytes, MEM_COMMIT, prot) == nullptr) { return win32_error(); @@ -693,12 +693,12 @@ result<map_handle::buffer_type> map_handle::commit(buffer_type region, section_h result<map_handle::buffer_type> map_handle::decommit(buffer_type region) noexcept { LLFIO_LOG_FUNCTION_CALL(this); - if(region.data == nullptr) + if(region.data() == nullptr) { return errc::invalid_argument; } region = utils::round_to_page_size(region); - OUTCOME_TRYV(win32_maps_apply(region.data, region.len, [](byte *addr, size_t bytes) -> result<void> { + OUTCOME_TRYV(win32_maps_apply(region.data(), region.size(), [](byte *addr, size_t bytes) -> result<void> { if(VirtualFree(addr, bytes, MEM_DECOMMIT) == 0) { return win32_error(); @@ -713,18 +713,18 @@ result<void> map_handle::zero_memory(buffer_type region) noexcept windows_nt_kernel::init(); using namespace windows_nt_kernel; LLFIO_LOG_FUNCTION_CALL(this); - if(region.data == nullptr) + if(region.data() == nullptr) { return errc::invalid_argument; } // Alas, zero() will not work on mapped views on Windows :(, so memset to zero and call discard if available - memset(region.data, 0, region.len); - if((DiscardVirtualMemory_ != nullptr) && region.len >= utils::page_size()) + memset(region.data(), 0, region.size()); + if((DiscardVirtualMemory_ != nullptr) && region.size() >= utils::page_size()) { region = utils::round_to_page_size(region); - if(region.len > 0) + if(region.size() > 0) { - OUTCOME_TRYV(win32_maps_apply(region.data, region.len, [](byte *addr, size_t bytes) -> result<void> { + OUTCOME_TRYV(win32_maps_apply(region.data(), region.size(), [](byte *addr, size_t bytes) -> result<void> { if(DiscardVirtualMemory_(addr, bytes) == 0) { return win32_error(); @@ -759,7 +759,7 @@ result<map_handle::buffer_type> map_handle::do_not_store(buffer_type region) noe using namespace windows_nt_kernel; LLFIO_LOG_FUNCTION_CALL(0); region = utils::round_to_page_size(region); - if(region.data == nullptr) + if(region.data() == nullptr) { return errc::invalid_argument; } @@ -769,7 +769,7 @@ result<map_handle::buffer_type> map_handle::do_not_store(buffer_type region) noe // Win8's DiscardVirtualMemory is much faster if it's available if(DiscardVirtualMemory_ != nullptr) { - OUTCOME_TRYV(win32_maps_apply(region.data, region.len, [](byte *addr, size_t bytes) -> result<void> { + OUTCOME_TRYV(win32_maps_apply(region.data(), region.size(), [](byte *addr, size_t bytes) -> result<void> { if(DiscardVirtualMemory_(addr, bytes) == 0) { return win32_error(); @@ -779,7 +779,7 @@ result<map_handle::buffer_type> map_handle::do_not_store(buffer_type region) noe return region; } // Else MEM_RESET will do - OUTCOME_TRYV(win32_maps_apply(region.data, region.len, [](byte *addr, size_t bytes) -> result<void> { + OUTCOME_TRYV(win32_maps_apply(region.data(), region.size(), [](byte *addr, size_t bytes) -> result<void> { if(VirtualAlloc(addr, bytes, MEM_RESET, 0) == nullptr) { return win32_error(); @@ -789,7 +789,7 @@ result<map_handle::buffer_type> map_handle::do_not_store(buffer_type region) noe return region; } // We did nothing - region.len = 0; + region = {region.data(), 0}; return region; } @@ -802,17 +802,17 @@ map_handle::io_result<map_handle::buffers_type> map_handle::read(io_request<buff { if(togo != 0u) { - req.data = addr; - if(req.len > togo) + req = {addr, req.size()}; + if(req.size() > togo) { - req.len = togo; + req = {req.data(), togo}; } - addr += req.len; - togo -= req.len; + addr += req.size(); + togo -= req.size(); } else { - req.len = 0; + req = {req.data(), 0}; } } return reqs.buffers; @@ -829,18 +829,18 @@ map_handle::io_result<map_handle::const_buffers_type> map_handle::write(io_reque { if(togo != 0u) { - if(req.len > togo) + if(req.size() > togo) { - req.len = togo; + req = {req.data(), togo}; } - memcpy(addr, req.data, req.len); - req.data = addr; - addr += req.len; - togo -= req.len; + memcpy(addr, req.data(), req.size()); + req = {addr, req.size()}; + addr += req.size(); + togo -= req.size(); } else { - req.len = 0; + req = {req.data(), 0}; } } return false; diff --git a/include/llfio/v2.0/detail/impl/windows/symlink_handle.ipp b/include/llfio/v2.0/detail/impl/windows/symlink_handle.ipp new file mode 100644 index 00000000..f9138f78 --- /dev/null +++ b/include/llfio/v2.0/detail/impl/windows/symlink_handle.ipp @@ -0,0 +1,283 @@ +/* A handle to a symbolic link +(C) 2018 Niall Douglas <http://www.nedproductions.biz/> (20 commits) +File Created: Jul 2018 + + +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 "../../../symlink_handle.hpp" +#include "import.hpp" + +LLFIO_V2_NAMESPACE_BEGIN + +result<symlink_handle> symlink_handle::clone(mode mode_, deadline /*unused*/) const noexcept +{ + LLFIO_LOG_FUNCTION_CALL(this); + result<symlink_handle> ret(symlink_handle(native_handle_type(), _devid, _inode, _flags)); + OUTCOME_TRY(do_clone_handle(ret.value()._v, _v, mode_, caching::all, _flags)); + return ret; +} + +LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result<symlink_handle> symlink_handle::symlink(const path_handle &base, symlink_handle::path_view_type path, symlink_handle::mode _mode, symlink_handle::creation _creation, flag flags) noexcept +{ + windows_nt_kernel::init(); + using namespace windows_nt_kernel; + result<symlink_handle> ret(symlink_handle(native_handle_type(), 0, 0, flags)); + native_handle_type &nativeh = ret.value()._v; + LLFIO_LOG_FUNCTION_CALL(&ret); + nativeh.behaviour |= native_handle_type::disposition::symlink; + DWORD fileshare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + if(_mode == mode::append || _creation == creation::truncate) + { + return errc::function_not_supported; + } + OUTCOME_TRY(access, access_mask_from_handle_mode(nativeh, _mode, flags)); + OUTCOME_TRY(attribs, attributes_from_handle_caching_and_flags(nativeh, caching::all, flags)); + nativeh.behaviour &= ~native_handle_type::disposition::seekable; // not seekable + if(base.is_valid() || path.is_ntpath()) + { + DWORD creatdisp = 0x00000001 /*FILE_OPEN*/; + switch(_creation) + { + case creation::open_existing: + break; + case creation::only_if_not_exist: + creatdisp = 0x00000002 /*FILE_CREATE*/; + break; + case creation::if_needed: + creatdisp = 0x00000003 /*FILE_OPEN_IF*/; + break; + case creation::truncate: + creatdisp = 0x00000004 /*FILE_OVERWRITE*/; + break; + } + + attribs &= 0x00ffffff; // the real attributes only, not the win32 flags + OUTCOME_TRY(ntflags, ntflags_from_handle_caching_and_flags(nativeh, caching::all, flags)); + ntflags |= 0x4000 /*FILE_OPEN_FOR_BACKUP_INTENT*/ | 0x00200000 /*FILE_OPEN_REPARSE_POINT*/; + ntflags |= 0x040 /*FILE_NON_DIRECTORY_FILE*/; // do not open a directory + IO_STATUS_BLOCK isb = make_iostatus(); + + path_view::c_str zpath(path, true); + UNICODE_STRING _path{}; + _path.Buffer = const_cast<wchar_t *>(zpath.buffer); + _path.MaximumLength = (_path.Length = static_cast<USHORT>(zpath.length * sizeof(wchar_t))) + sizeof(wchar_t); + if(zpath.length >= 4 && _path.Buffer[0] == '\\' && _path.Buffer[1] == '!' && _path.Buffer[2] == '!' && _path.Buffer[3] == '\\') + { + _path.Buffer += 3; + _path.Length -= 3 * sizeof(wchar_t); + _path.MaximumLength -= 3 * sizeof(wchar_t); + } + + OBJECT_ATTRIBUTES oa{}; + memset(&oa, 0, sizeof(oa)); + oa.Length = sizeof(OBJECT_ATTRIBUTES); + oa.ObjectName = &_path; + oa.RootDirectory = base.is_valid() ? base.native_handle().h : nullptr; + oa.Attributes = 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); + } + } + else + { + DWORD creation = OPEN_EXISTING; + switch(_creation) + { + case creation::open_existing: + break; + case creation::only_if_not_exist: + creation = CREATE_NEW; + break; + case creation::if_needed: + creation = OPEN_ALWAYS; + break; + case creation::truncate: + creation = TRUNCATE_EXISTING; + break; + } + // required to open a symlink + attribs |= FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT; + path_view::c_str zpath(path, false); + if(INVALID_HANDLE_VALUE == (nativeh.h = CreateFileW_(zpath.buffer, access, fileshare, nullptr, creation, attribs, nullptr))) // NOLINT + { + DWORD errcode = GetLastError(); + // assert(false); + return win32_error(errcode); + } + } + return ret; +} + +result<symlink_handle::buffers_type> symlink_handle::read(symlink_handle::io_request<symlink_handle::buffers_type> req) noexcept +{ + windows_nt_kernel::init(); + using namespace windows_nt_kernel; + LLFIO_LOG_FUNCTION_CALL(this); + using windows_nt_kernel::REPARSE_DATA_BUFFER; + symlink_handle::buffers_type tofill; + if(req.kernelbuffer.empty()) + { + // Let's assume the average symbolic link will be 256 characters long. + size_t toallocate = (sizeof(FILE_ID_FULL_DIR_INFORMATION) + 256 * sizeof(wchar_t)); + auto *mem = new(std::nothrow) char[toallocate]; + if(mem == nullptr) + { + return errc::not_enough_memory; + } + tofill._kernel_buffer = std::unique_ptr<char[]>(mem); + tofill._kernel_buffer_size = toallocate; + } + REPARSE_DATA_BUFFER *rpd; + size_t bytes; + for(;;) + { + rpd = req.kernelbuffer.empty() ? reinterpret_cast<REPARSE_DATA_BUFFER *>(tofill._kernel_buffer.get()) : reinterpret_cast<REPARSE_DATA_BUFFER *>(req.kernelbuffer.data()); + bytes = req.kernelbuffer.empty() ? static_cast<ULONG>(tofill._kernel_buffer_size) : static_cast<ULONG>(req.kernelbuffer.size()); + DWORD written = 0; + if(!DeviceIoControl(_v.h, FSCTL_GET_REPARSE_POINT, NULL, 0, rpd, (DWORD) bytes, &written, NULL)) + { + DWORD errcode = GetLastError(); + if(req.kernelbuffer.empty() && (errcode == ERROR_INSUFFICIENT_BUFFER || errcode == ERROR_MORE_DATA)) + { + tofill._kernel_buffer.reset(); + size_t toallocate = tofill._kernel_buffer_size * 2; + auto *mem = new(std::nothrow) char[toallocate]; + if(mem == nullptr) + { + return errc::not_enough_memory; + } + tofill._kernel_buffer = std::unique_ptr<char[]>(mem); + tofill._kernel_buffer_size = toallocate; + continue; + } + return win32_error(errcode); + } + switch(rpd->ReparseTag) + { + case IO_REPARSE_TAG_MOUNT_POINT: + tofill._link = path_view(rpd->MountPointReparseBuffer.PathBuffer + rpd->MountPointReparseBuffer.SubstituteNameOffset / sizeof(rpd->MountPointReparseBuffer.PathBuffer[0]), rpd->MountPointReparseBuffer.SubstituteNameLength / sizeof(rpd->MountPointReparseBuffer.PathBuffer[0])); + tofill._type = symlink_type::win_junction; + return std::move(tofill); + case IO_REPARSE_TAG_SYMLINK: + tofill._link = path_view(rpd->SymbolicLinkReparseBuffer.PathBuffer + rpd->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(rpd->SymbolicLinkReparseBuffer.PathBuffer[0]), rpd->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(rpd->SymbolicLinkReparseBuffer.PathBuffer[0])); + tofill._type = symlink_type::symbolic; + return std::move(tofill); + } + return errc::protocol_not_supported; + } +} + +result<symlink_handle::const_buffers_type> symlink_handle::write(symlink_handle::io_request<symlink_handle::const_buffers_type> req) noexcept +{ + windows_nt_kernel::init(); + using namespace windows_nt_kernel; + LLFIO_LOG_FUNCTION_CALL(this); + using windows_nt_kernel::REPARSE_DATA_BUFFER; + size_t destpathbytes = req.buffers.path().native_size() * sizeof(wchar_t); + size_t buffersize = sizeof(REPARSE_DATA_BUFFER) + destpathbytes * 2 + 256; + if(buffersize < req.kernelbuffer.size()) + { + return errc::not_enough_memory; + } + const size_t headerlen = offsetof(REPARSE_DATA_BUFFER, MountPointReparseBuffer); + auto *buffer = req.kernelbuffer.empty() ? alloca(buffersize) : req.kernelbuffer.data(); + memset(buffer, 0, sizeof(REPARSE_DATA_BUFFER)); + auto *rpd = (REPARSE_DATA_BUFFER *) buffer; + path_view::c_str zpath(req.buffers.path(), true); + switch(req.buffers.type()) + { + case symlink_type::none: + return errc::invalid_argument; + case symlink_type::symbolic: + { + const size_t reparsebufferheaderlen = offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) - headerlen; + rpd->ReparseTag = IO_REPARSE_TAG_SYMLINK; + if(zpath.length >= 4 && zpath.buffer[0] == '\\' && zpath.buffer[1] == '!' && zpath.buffer[2] == '!' && zpath.buffer[3] == '\\') + { + memcpy(rpd->SymbolicLinkReparseBuffer.PathBuffer, zpath.buffer + 3, destpathbytes - 6 + sizeof(wchar_t)); + rpd->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0; + rpd->SymbolicLinkReparseBuffer.SubstituteNameLength = (USHORT) destpathbytes - 6; + rpd->SymbolicLinkReparseBuffer.PrintNameOffset = (USHORT)(destpathbytes - 6 + sizeof(wchar_t)); + rpd->SymbolicLinkReparseBuffer.PrintNameLength = (USHORT) destpathbytes - 6; + memcpy(rpd->SymbolicLinkReparseBuffer.PathBuffer + rpd->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(wchar_t), zpath.buffer + 3, rpd->SymbolicLinkReparseBuffer.PrintNameLength - 6 + sizeof(wchar_t)); + } + else + { + memcpy(rpd->SymbolicLinkReparseBuffer.PathBuffer, zpath.buffer, destpathbytes + sizeof(wchar_t)); + rpd->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0; + rpd->SymbolicLinkReparseBuffer.SubstituteNameLength = (USHORT) destpathbytes; + rpd->SymbolicLinkReparseBuffer.PrintNameOffset = (USHORT)(destpathbytes + sizeof(wchar_t)); + rpd->SymbolicLinkReparseBuffer.PrintNameLength = (USHORT) destpathbytes; + memcpy(rpd->SymbolicLinkReparseBuffer.PathBuffer + rpd->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(wchar_t), zpath.buffer, rpd->SymbolicLinkReparseBuffer.PrintNameLength + sizeof(wchar_t)); + } + rpd->SymbolicLinkReparseBuffer.Flags = req.buffers.path().is_relative() ? 0x1 /*SYMLINK_FLAG_RELATIVE*/ : 0; + rpd->ReparseDataLength = (USHORT)(rpd->SymbolicLinkReparseBuffer.SubstituteNameLength + rpd->SymbolicLinkReparseBuffer.PrintNameLength + 2 * sizeof(wchar_t) + reparsebufferheaderlen); + break; + } + case symlink_type::win_wsl: + // TODO FIXME + abort(); + case symlink_type::win_junction: + { + const size_t reparsebufferheaderlen = offsetof(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer) - headerlen; + rpd->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; + if(zpath.length >= 4 && zpath.buffer[0] == '\\' && zpath.buffer[1] == '!' && zpath.buffer[2] == '!' && zpath.buffer[3] == '\\') + { + memcpy(rpd->MountPointReparseBuffer.PathBuffer, zpath.buffer + 3, destpathbytes - 6 + sizeof(wchar_t)); + rpd->MountPointReparseBuffer.SubstituteNameOffset = 0; + rpd->MountPointReparseBuffer.SubstituteNameLength = (USHORT) destpathbytes - 6; + rpd->MountPointReparseBuffer.PrintNameOffset = (USHORT)(destpathbytes - 6 + sizeof(wchar_t)); + rpd->MountPointReparseBuffer.PrintNameLength = (USHORT) destpathbytes - 6; + memcpy(rpd->MountPointReparseBuffer.PathBuffer + rpd->MountPointReparseBuffer.PrintNameOffset / sizeof(wchar_t), zpath.buffer + 3, rpd->MountPointReparseBuffer.PrintNameLength - 6 + sizeof(wchar_t)); + } + else + { + memcpy(rpd->MountPointReparseBuffer.PathBuffer, zpath.buffer, destpathbytes + sizeof(wchar_t)); + rpd->MountPointReparseBuffer.SubstituteNameOffset = 0; + rpd->MountPointReparseBuffer.SubstituteNameLength = (USHORT) destpathbytes; + rpd->MountPointReparseBuffer.PrintNameOffset = (USHORT)(destpathbytes + sizeof(wchar_t)); + rpd->MountPointReparseBuffer.PrintNameLength = (USHORT) destpathbytes; + memcpy(rpd->MountPointReparseBuffer.PathBuffer + rpd->MountPointReparseBuffer.PrintNameOffset / sizeof(wchar_t), zpath.buffer, rpd->MountPointReparseBuffer.PrintNameLength + sizeof(wchar_t)); + } + rpd->ReparseDataLength = (USHORT)(rpd->MountPointReparseBuffer.SubstituteNameLength + rpd->MountPointReparseBuffer.PrintNameLength + 2 * sizeof(wchar_t) + reparsebufferheaderlen); + break; + } + } + DWORD bytesout = 0; + if(DeviceIoControl(_v.h, FSCTL_SET_REPARSE_POINT, rpd, (DWORD)(rpd->ReparseDataLength + headerlen), NULL, 0, &bytesout, NULL) == 0) + { + return win32_error(); + } + return success(std::move(req.buffers)); +} + +LLFIO_V2_NAMESPACE_END diff --git a/include/llfio/v2.0/io_handle.hpp b/include/llfio/v2.0/io_handle.hpp index 58716368..55aa1f21 100644 --- a/include/llfio/v2.0/io_handle.hpp +++ b/include/llfio/v2.0/io_handle.hpp @@ -51,10 +51,13 @@ public: using flag = handle::flag; //! The scatter buffer type used by this handle. Guaranteed to be `TrivialType` and `StandardLayoutType`. + //! Try to make address and length 64 byte, or ideally, `page_size()` aligned where possible. struct buffer_type { //! Type of the pointer to memory. using pointer = byte *; + //! Type of the pointer to memory. + using const_pointer = const byte *; //! Type of the iterator to memory. using iterator = byte *; //! Type of the iterator to memory. @@ -62,29 +65,50 @@ public: //! Type of the length of memory. using size_type = size_t; - //! Pointer to memory to be filled by a read. Try to make this 64 byte, or ideally, `page_size()` aligned where possible. - pointer data; - //! The number of bytes to fill into this address. Try to make this a 64 byte multiple, or ideally, a whole multiple of `page_size()`. - size_type len; + //! Default constructor + buffer_type() = default; + //! Constructor + constexpr buffer_type(pointer data, size_type len) noexcept : _data(data), _len(len) {} + buffer_type(const buffer_type &) = default; + buffer_type(buffer_type &&) = default; + buffer_type &operator=(const buffer_type &) = default; + buffer_type &operator=(buffer_type &&) = default; + ~buffer_type() = default; + + // Emulation of this being a span<byte> in the TS + + //! Returns the address of the bytes for this buffer + constexpr pointer data() noexcept { return _data; } + //! Returns the address of the bytes for this buffer + constexpr const_pointer data() const noexcept { return _data; } + //! Returns the number of bytes in this buffer + constexpr size_type size() const noexcept { return _len; } //! Returns an iterator to the beginning of the buffer - constexpr iterator begin() { return data; } + constexpr iterator begin() noexcept { return _data; } //! Returns an iterator to the beginning of the buffer - constexpr const_iterator begin() const { return data; } + constexpr const_iterator begin() const noexcept { return _data; } //! Returns an iterator to the beginning of the buffer - constexpr const_iterator cbegin() const { return data; } + constexpr const_iterator cbegin() const noexcept { return _data; } //! Returns an iterator to after the end of the buffer - constexpr iterator end() { return data + len; } + constexpr iterator end() noexcept { return _data + _len; } //! Returns an iterator to after the end of the buffer - constexpr const_iterator end() const { return data + len; } + constexpr const_iterator end() const noexcept { return _data + _len; } //! Returns an iterator to after the end of the buffer - constexpr const_iterator cend() const { return data + len; } + constexpr const_iterator cend() const noexcept { return _data + _len; } + private: + friend constexpr inline void _check_iovec_match(); + pointer _data; + size_type _len; }; //! The gather buffer type used by this handle. Guaranteed to be `TrivialType` and `StandardLayoutType`. + //! Try to make address and length 64 byte, or ideally, `page_size()` aligned where possible. struct const_buffer_type { //! Type of the pointer to memory. using pointer = const byte *; + //! Type of the pointer to memory. + using const_pointer = const byte *; //! Type of the iterator to memory. using iterator = const byte *; //! Type of the iterator to memory. @@ -92,23 +116,40 @@ public: //! Type of the length of memory. using size_type = size_t; - //! Pointer to memory to be written. Try to make this 64 byte, or ideally, `page_size()` aligned where possible. - pointer data; - //! The number of bytes to write from this address. Try to make this a 64 byte multiple, or ideally, a whole multiple of `page_size()`. - size_type len; + //! Default constructor + const_buffer_type() = default; + //! Constructor + constexpr const_buffer_type(pointer data, size_type len) noexcept : _data(data), _len(len) {} + const_buffer_type(const const_buffer_type &) = default; + const_buffer_type(const_buffer_type &&) = default; + const_buffer_type &operator=(const const_buffer_type &) = default; + const_buffer_type &operator=(const_buffer_type &&) = default; + ~const_buffer_type() = default; + + // Emulation of this being a span<byte> in the TS + + //! Returns the address of the bytes for this buffer + constexpr pointer data() noexcept { return _data; } + //! Returns the address of the bytes for this buffer + constexpr const_pointer data() const noexcept { return _data; } + //! Returns the number of bytes in this buffer + constexpr size_type size() const noexcept { return _len; } //! Returns an iterator to the beginning of the buffer - constexpr iterator begin() { return data; } + constexpr iterator begin() noexcept { return _data; } //! Returns an iterator to the beginning of the buffer - constexpr const_iterator begin() const { return data; } + constexpr const_iterator begin() const noexcept { return _data; } //! Returns an iterator to the beginning of the buffer - constexpr const_iterator cbegin() const { return data; } + constexpr const_iterator cbegin() const noexcept { return _data; } //! Returns an iterator to after the end of the buffer - constexpr iterator end() { return data + len; } + constexpr iterator end() noexcept { return _data + _len; } //! Returns an iterator to after the end of the buffer - constexpr const_iterator end() const { return data + len; } + constexpr const_iterator end() const noexcept { return _data + _len; } //! Returns an iterator to after the end of the buffer - constexpr const_iterator cend() const { return data + len; } + constexpr const_iterator cend() const noexcept { return _data + _len; } + private: + pointer _data; + size_type _len; }; #ifndef NDEBUG static_assert(std::is_trivial<buffer_type>::value, "buffer_type is not a trivial type!"); @@ -459,11 +500,11 @@ public: size_t bytes = 0; for(auto &i : reqs.buffers) { - if(bytes + i.len < bytes) + if(bytes + i.size() < bytes) { return errc::value_too_large; } - bytes += i.len; + bytes += i.size(); } return lock(reqs.offset, bytes, false, d); } @@ -473,11 +514,11 @@ public: size_t bytes = 0; for(auto &i : reqs.buffers) { - if(bytes + i.len < bytes) + if(bytes + i.size() < bytes) { return errc::value_too_large; } - bytes += i.len; + bytes += i.size(); } return lock(reqs.offset, bytes, true, d); } diff --git a/include/llfio/v2.0/llfio.hpp b/include/llfio/v2.0/llfio.hpp index 4fee0f54..59984dca 100644 --- a/include/llfio/v2.0/llfio.hpp +++ b/include/llfio/v2.0/llfio.hpp @@ -73,6 +73,7 @@ import LLFIO_MODULE_NAME; #ifndef LLFIO_LEAN_AND_MEAN #include "storage_profile.hpp" #endif +#include "symlink_handle.hpp" #include "algorithm/cached_parent_handle_adapter.hpp" #include "algorithm/shared_fs_mutex/atomic_append.hpp" diff --git a/include/llfio/v2.0/map_handle.hpp b/include/llfio/v2.0/map_handle.hpp index d3a8c2df..60deb440 100644 --- a/include/llfio/v2.0/map_handle.hpp +++ b/include/llfio/v2.0/map_handle.hpp @@ -362,15 +362,15 @@ public: LLFIO_MAKE_FREE_FUNCTION static const_buffer_type barrier(const_buffer_type req, bool evict = false) noexcept { - const_buffer_type ret{(const_buffer_type::pointer)(((uintptr_t) req.data) & 31), 0}; - ret.len = req.data + req.len - ret.data; - for(const_buffer_type::pointer addr = ret.data; addr < ret.data + ret.len; addr += 32) + auto *tp = (const_buffer_type::pointer)(((uintptr_t) req.data()) & 31); + const_buffer_type ret{tp, (size_t)(req.data() + req.size() - tp)}; + for(const_buffer_type::pointer addr = ret.data(); addr < ret.data() + ret.size(); addr += 32) { // Slightly UB ... auto *p = reinterpret_cast<const persistent<byte> *>(addr); if(memory_flush_none == p->flush(evict ? memory_flush_evict : memory_flush_retain)) { - req.len = 0; + ret = {tp, 0}; break; } } diff --git a/include/llfio/v2.0/path_view.hpp b/include/llfio/v2.0/path_view.hpp index 23ec86a5..73faec7a 100644 --- a/include/llfio/v2.0/path_view.hpp +++ b/include/llfio/v2.0/path_view.hpp @@ -122,16 +122,6 @@ maximum compatibility you should still use the Win32 API. class LLFIO_DECL path_view { public: - //! Character type - using value_type = char; - //! Pointer type - using pointer = char *; - //! Const pointer type - using const_pointer = const char *; - //! Reference type - using reference = char &; - //! Const reference type - using const_reference = const char &; // const_iterator // iterator // reverse_iterator @@ -141,6 +131,9 @@ public: //! Difference type using difference_type = std::ptrdiff_t; + //! The preferred separator type + static constexpr auto preferred_separator = filesystem::path::preferred_separator; + private: static constexpr auto _npos = string_view::npos; #ifdef _WIN32 @@ -167,6 +160,16 @@ private: } _state; template <class U> constexpr auto _invoke(U &&f) noexcept { return !_state._utf16.empty() ? f(_state._utf16) : f(_state._utf8); } template <class U> constexpr auto _invoke(U &&f) const noexcept { return !_state._utf16.empty() ? f(_state._utf16) : f(_state._utf8); } + constexpr auto _find_first_sep(size_t startidx = 0) const noexcept + { + // wchar paths must use backslashes + if(!_state._utf16.empty()) + { + return _state._utf16.find('\\', startidx); + } + // char paths can use either + return _state._utf8.find_first_of("/\\", startidx); + } constexpr auto _find_last_sep() const noexcept { // wchar paths must use backslashes @@ -191,7 +194,8 @@ private: } _state; template <class U> constexpr auto _invoke(U &&f) noexcept { return f(_state._utf8); } template <class U> constexpr auto _invoke(U &&f) const noexcept { return f(_state._utf8); } - constexpr auto _find_last_sep() const noexcept { return _state._utf8.rfind(filesystem::path::preferred_separator); } + constexpr auto _find_first_sep(size_t startidx = 0) const noexcept { return _state._utf8.find(preferred_separator, startidx); } + constexpr auto _find_last_sep() const noexcept { return _state._utf8.rfind(preferred_separator); } #endif public: //! Constructs an empty path view @@ -252,16 +256,38 @@ public: { return _invoke([](const auto &v) { return v.empty(); }); } - constexpr bool has_root_path() const noexcept; - constexpr bool has_root_name() const noexcept; - constexpr bool has_root_directory() const noexcept; - constexpr bool has_relative_path() const noexcept; - constexpr bool has_parent_path() const noexcept; - constexpr bool has_filename() const noexcept; - constexpr bool has_stem() const noexcept; - constexpr bool has_extension() const noexcept; - constexpr bool is_absolute() const noexcept; - constexpr bool is_relative() const noexcept; + constexpr bool has_root_path() const noexcept { return !root_path().empty(); } + constexpr bool has_root_name() const noexcept { return !root_name().empty(); } + constexpr bool has_root_directory() const noexcept { return !root_directory().empty(); } + constexpr bool has_relative_path() const noexcept { return !relative_path().empty(); } + constexpr bool has_parent_path() const noexcept { return !parent_path().empty(); } + constexpr bool has_filename() const noexcept { return !filename().empty(); } + constexpr bool has_stem() const noexcept { return !stem().empty(); } + constexpr bool has_extension() const noexcept { return !extension().empty(); } + constexpr bool is_absolute() const noexcept + { + auto sep_idx = _find_first_sep(); + if(_npos == sep_idx) + { + return false; + } +#ifdef _WIN32 + if(is_ntpath()) + return true; + return _invoke([sep_idx](const auto &v) { + if(sep_idx == 0) + { + if(v[sep_idx + 1] == preferred_separator) // double separator at front + return true; + } + auto colon_idx = v.find(':'); + return colon_idx < sep_idx; // colon before first separator + }); +#else + return sep_idx == 0; +#endif + } + constexpr bool is_relative() const noexcept { return !is_absolute(); } // True if the path view contains any of the characters `*`, `?`, (POSIX only: `[` or `]`). constexpr bool contains_glob() const noexcept { @@ -342,11 +368,109 @@ public: { return _invoke([](const auto &v) { return v.size(); }); } - constexpr path_view root_name() const noexcept; - constexpr path_view root_directory() const noexcept; - constexpr path_view root_path() const noexcept; - constexpr path_view relative_path() const noexcept; - constexpr path_view parent_path() const noexcept; + //! Returns a view of the root name part of this view e.g. C: + constexpr path_view root_name() const noexcept + { + auto sep_idx = _find_first_sep(); + if(_npos == sep_idx) + { + return path_view(); + } + return _invoke([sep_idx](const auto &v) { return path_view(v.data(), sep_idx); }); + } + //! Returns a view of the root directory, if there is one e.g. / + constexpr path_view root_directory() const noexcept + { + auto sep_idx = _find_first_sep(); + if(_npos == sep_idx) + { + return path_view(); + } + return _invoke([sep_idx](const auto &v) { +#ifdef _WIN32 + auto colon_idx = v.find(':'); + if(colon_idx < sep_idx) + { + return path_view(v.data() + sep_idx, 1); + } +#endif + if(sep_idx == 0) + { + return path_view(v.data(), 1); + } + return path_view(); + }); + } + //! Returns, if any, a view of the root path part of this view e.g. C:/ + constexpr path_view root_path() const noexcept + { + auto sep_idx = _find_first_sep(); + if(_npos == sep_idx) + { + return path_view(); + } + return _invoke([this, sep_idx](const auto &v) { +#ifdef _WIN32 + if(is_ntpath()) + { + return path_view(v.data() + 3, 1); + } + // Special case \\.\ and \\?\ to match filesystem::path + if(v.size() >= 4 && sep_idx == 0 && v[1] == '\\' && (v[2] == '.' || v[2] == '?') && v[3] == '\\') + { + return path_view(v.data() + 0, 4); + } + auto colon_idx = v.find(':'); + if(colon_idx < sep_idx) + { + return path_view(v.data(), sep_idx + 1); + } +#endif + if(sep_idx == 0) + { + return path_view(v.data(), 1); + } + return path_view(); + }); + } + //! Returns a view of everything after the root path + constexpr path_view relative_path() const noexcept + { + auto sep_idx = _find_first_sep(); + if(_npos == sep_idx) + { + return *this; + } + return _invoke([this, sep_idx](const auto &v) { +#ifdef _WIN32 + // Special case \\.\ and \\?\ to match filesystem::path + if(v.size() >= 4 && sep_idx == 0 && v[1] == '\\' && (v[2] == '.' || v[2] == '?') && v[3] == '\\') + { + return path_view(v.data() + 4, v.size() - 4); + } + auto colon_idx = v.find(':'); + if(colon_idx < sep_idx) + { + return path_view(v.data() + sep_idx + 1, v.size() - sep_idx - 1); + } +#endif + if(sep_idx == 0) + { + return path_view(v.data() + 1, v.size() - 1); + } + return path_view(v.data(), v.size()); + }); + } + //! Returns a view of the everything apart from the filename part of this view + constexpr path_view parent_path() const noexcept + { + auto sep_idx = _find_last_sep(); + if(_npos == sep_idx) + { + return path_view(); + } + return _invoke([sep_idx](const auto &v) { return path_view(v.data(), sep_idx); }); + } //! Returns a view of the filename part of this view. constexpr path_view filename() const noexcept { @@ -357,8 +481,32 @@ public: } return _invoke([sep_idx](const auto &v) { return path_view(v.data() + sep_idx + 1, v.size() - sep_idx - 1); }); } - constexpr path_view stem() const noexcept; - constexpr path_view extension() const noexcept; + //! Returns a view of the filename without any file extension + constexpr path_view stem() const noexcept + { + auto sep_idx = _find_last_sep(); + return _invoke([sep_idx](const auto &v) { + auto dot_idx = v.rfind('.'); + if(_npos == dot_idx || (_npos != sep_idx && dot_idx < sep_idx) || dot_idx == sep_idx + 1 || (dot_idx == sep_idx + 2 && v[dot_idx - 1] == '.')) + { + return path_view(v.data() + sep_idx + 1, v.size() - sep_idx - 1); + } + return path_view(v.data() + sep_idx + 1, dot_idx - sep_idx - 1); + }); + } + //! Returns a view of the file extension part of this view + constexpr path_view extension() const noexcept + { + auto sep_idx = _find_last_sep(); + return _invoke([sep_idx](const auto &v) { + auto dot_idx = v.rfind('.'); + if(_npos == dot_idx || (_npos != sep_idx && dot_idx < sep_idx) || dot_idx == sep_idx + 1 || (dot_idx == sep_idx + 2 && v[dot_idx - 1] == '.')) + { + return path_view(); + } + return path_view(v.data() + dot_idx, v.size() - dot_idx); + }); + } //! Return the path view as a path. filesystem::path path() const diff --git a/include/llfio/v2.0/symlink_handle.hpp b/include/llfio/v2.0/symlink_handle.hpp new file mode 100644 index 00000000..179b820a --- /dev/null +++ b/include/llfio/v2.0/symlink_handle.hpp @@ -0,0 +1,417 @@ +/* A handle to a symbolic link +(C) 2018 Niall Douglas <http://www.nedproductions.biz/> (20 commits) +File Created: Jul 2018 + + +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) +*/ + +#ifndef LLFIO_SYMLINK_HANDLE_H +#define LLFIO_SYMLINK_HANDLE_H + +#include "handle.hpp" +#include "path_view.hpp" + +//! \file symlink_handle.hpp Provides a handle to a symbolic link. + +#ifndef LLFIO_SYMLINK_HANDLE_IS_FAKED +#if defined(_WIN32) || defined(__Linux__) +#define LLFIO_SYMLINK_HANDLE_IS_FAKED 0 +#else +#define LLFIO_SYMLINK_HANDLE_IS_FAKED 1 +#endif +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4251) // dll interface +#endif + +LLFIO_V2_NAMESPACE_EXPORT_BEGIN + +class symlink_handle; + +/*! \class symlink_handle +\brief A handle to an inode which redirects to a different path. + +Microsoft Windows and Linux provide the ability to open the contents of a symbolic link directly, +for those platforms this handle works exactly like any ordinary handle. For other POSIX platforms +without proprietary extensions, it is not possible to get a valid file descriptor to the contents +of a symlink, and in this situation the native handle returned will be `-1` and the preprocessor +macro `LLFIO_SYMLINK_HANDLE_IS_FAKED` will be non-zero. + +If `LLFIO_SYMLINK_HANDLE_IS_FAKED` is on, the handle is race free up to the containing directory +only. If a third party relocates the symbolic link into a different directory, and race free +checking is enabled, this class will simply refuse to work as it no longer has any way of finding +the symbolic link. You should take care that this does not become a denial of service attack. + +On Microsoft Windows, there are many kinds of symbolic link: this implementation supports +directory junctions, NTFS symbolic links and WSL symbolic links. Any others will return an error +code comparing equal to `errc::protocol_not_supported`. One should note that modifying symbolic +links is not permitted by users with ordinary permissions on Microsoft Windows, however when +Windows is in Developer Mode, they are. This can cause developer testing to be an inaccurate +view of the user experience. Windows supports directory symbolic links (junctions), these work +for all users in any configuration. +*/ +class LLFIO_DECL symlink_handle : public handle, public fs_handle +{ +#if LLFIO_SYMLINK_HANDLE_IS_FAKED + // Need to retain a handle to our base and our leafname + path_handle _dirh; + handle::path_type _leafname; +#endif + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC const handle &_get_handle() const noexcept final { return *this; } + +public: + using path_type = handle::path_type; + using extent_type = handle::extent_type; + using size_type = handle::size_type; + using mode = handle::mode; + using creation = handle::creation; + using caching = handle::caching; + using flag = handle::flag; + using dev_t = fs_handle::dev_t; + using ino_t = fs_handle::ino_t; + using path_view_type = fs_handle::path_view_type; + + //! The type of symbolic link this is + enum class symlink_type + { + none, //!<! No link + symbolic, //!< Standard symbolic link + + win_wsl, //!< WSL symbolic link (Windows only) + win_junction //!< NTFS directory junction (Windows only, directories and volumes only) + }; + + //! The buffer type used by this handle, which is a `path_view` + using buffer_type = path_view; + /*! The buffers type used by this handle for reads, which is a single item sequence of `path_view`. + + \warning Unless you supply your own kernel buffer, you need to keep this around as long as you + use the path view, as the path is a view of the original buffer filled by + the kernel and the existence of this keeps that original buffer around. + */ + struct buffers_type + { + //! Type of the pointer to the buffer. + using pointer = path_view *; + //! Type of the iterator to the buffer. + using iterator = path_view *; + //! Type of the iterator to the buffer. + using const_iterator = const path_view *; + //! Type of the length of the buffers. + using size_type = size_t; + + //! Default constructor + constexpr buffers_type() {} // NOLINT + + //! Constructor + constexpr buffers_type(path_view link, symlink_type type = symlink_type::symbolic) + : _link(link) + , _type(type) + { + } + ~buffers_type() = default; + //! Move constructor + buffers_type(buffers_type &&o) noexcept : _link(o._link), _type(o._type), _kernel_buffer(std::move(o._kernel_buffer)), _kernel_buffer_size(o._kernel_buffer_size) + { + o._link = {}; + o._type = symlink_type::none; + o._kernel_buffer_size = 0; + } + //! No copy construction + buffers_type(const buffers_type &) = delete; + //! Move assignment + buffers_type &operator=(buffers_type &&o) noexcept + { + this->~buffers_type(); + new(this) buffers_type(std::move(o)); + return *this; + } + //! No copy assignment + buffers_type &operator=(const buffers_type &) = delete; + + //! Returns an iterator to the beginning of the buffers + constexpr iterator begin() noexcept { return &_link; } + //! Returns an iterator to the beginning of the buffers + constexpr const_iterator begin() const noexcept { return &_link; } + //! Returns an iterator to the beginning of the buffers + constexpr const_iterator cbegin() const noexcept { return &_link; } + //! Returns an iterator to after the end of the buffers + constexpr iterator end() noexcept { return &_link + 1; } + //! Returns an iterator to after the end of the buffers + constexpr const_iterator end() const noexcept { return &_link + 1; } + //! Returns an iterator to after the end of the buffers + constexpr const_iterator cend() const noexcept { return &_link + 1; } + + //! The path referenced by the symbolic link + path_view path() const noexcept { return _link; } + //! The type of the symbolic link + symlink_type type() const noexcept { return _type; } + + private: + friend class symlink_handle; + path_view _link; + symlink_type _type{symlink_type::none}; + std::unique_ptr<char[]> _kernel_buffer; + size_t _kernel_buffer_size{0}; + }; + /*! The constant buffers type used by this handle for writes, which is a single item sequence of `path_view`. + */ + struct const_buffers_type + { + //! Type of the pointer to the buffer. + using pointer = const path_view *; + //! Type of the iterator to the buffer. + using iterator = const path_view *; + //! Type of the iterator to the buffer. + using const_iterator = const path_view *; + //! Type of the length of the buffers. + using size_type = size_t; + + //! Constructor + constexpr const_buffers_type(path_view link, symlink_type type = symlink_type::symbolic) + : _link(link) + , _type(type) + { + } + ~const_buffers_type() = default; + //! Move constructor + const_buffers_type(const_buffers_type &&o) noexcept : _link(o._link), _type(o._type) + { + o._link = {}; + o._type = symlink_type::none; + } + //! No copy construction + const_buffers_type(const buffers_type &) = delete; + //! Move assignment + const_buffers_type &operator=(const_buffers_type &&o) noexcept + { + this->~const_buffers_type(); + new(this) const_buffers_type(std::move(o)); + return *this; + } + //! No copy assignment + const_buffers_type &operator=(const const_buffers_type &) = delete; + + //! Returns an iterator to the beginning of the buffers + constexpr iterator begin() noexcept { return &_link; } + //! Returns an iterator to the beginning of the buffers + constexpr const_iterator begin() const noexcept { return &_link; } + //! Returns an iterator to the beginning of the buffers + constexpr const_iterator cbegin() const noexcept { return &_link; } + //! Returns an iterator to after the end of the buffers + constexpr iterator end() noexcept { return &_link + 1; } + //! Returns an iterator to after the end of the buffers + constexpr const_iterator end() const noexcept { return &_link + 1; } + //! Returns an iterator to after the end of the buffers + constexpr const_iterator cend() const noexcept { return &_link + 1; } + + //! The path referenced by the symbolic link + path_view path() const noexcept { return _link; } + //! The type of the symbolic link + symlink_type type() const noexcept { return _type; } + + private: + friend class symlink_handle; + path_view _link; + symlink_type _type{symlink_type::none}; + }; + //! The i/o request type used by this handle. Guaranteed to be `TrivialType` apart from construction, and `StandardLayoutType`. + template <class T> struct io_request; + //! Specialisation for reading symlinks + template <> struct io_request<buffers_type> + { + span<char> kernelbuffer{}; + + constexpr io_request() {} // NOLINT + //! Construct a request to read a link with optionally specified kernel buffer + constexpr io_request(span<char> _kernelbuffer) + : kernelbuffer(_kernelbuffer) + { + } + //! Convenience constructor constructing from anything a `span<char>` can construct from + LLFIO_TEMPLATE(class... Args) + LLFIO_TREQUIRES(LLFIO_TPRED(std::is_constructible<span<char>, Args...>::value)) + constexpr io_request(Args &&... args) noexcept : io_request(span<char>(static_cast<Args &&>(args)...)) {} + }; + //! Specialisation for writing symlinks + template <> struct io_request<const_buffers_type> + { + const_buffers_type buffers; + span<char> kernelbuffer; + //! Construct a request to write a link with optionally specified kernel buffer + constexpr io_request(const_buffers_type _buffers, span<char> _kernelbuffer = span<char>()) + : buffers(std::move(_buffers)) + , kernelbuffer(_kernelbuffer) + { + } + //! Convenience constructor constructing from anything a `path_view` can construct from + LLFIO_TEMPLATE(class... Args) + LLFIO_TREQUIRES(LLFIO_TPRED(std::is_constructible<path_view, Args...>::value)) + constexpr io_request(Args &&... args) noexcept : buffers(path_view(static_cast<Args &&>(args)...)) {} + //! Convenience constructor constructing a specific type of link from anything a `path_view` can construct from + LLFIO_TEMPLATE(class... Args) + LLFIO_TREQUIRES(LLFIO_TPRED(std::is_constructible<path_view, Args...>::value)) + constexpr io_request(symlink_type type, Args &&... args) noexcept : buffers(path_view(static_cast<Args &&>(args)...), type) {} + }; + +//! Default constructor +#if !LLFIO_SYMLINK_HANDLE_IS_FAKED + constexpr +#endif + symlink_handle() + { + } // NOLINT +//! Construct a handle from a supplied native handle +#if !LLFIO_SYMLINK_HANDLE_IS_FAKED + constexpr +#endif + explicit symlink_handle(native_handle_type h, dev_t devid, ino_t inode, flag flags = flag::none) + : handle(std::move(h), caching::all, flags) + , fs_handle(devid, inode) + { + } +//! Explicit conversion from handle permitted +#if !LLFIO_SYMLINK_HANDLE_IS_FAKED + constexpr +#endif + explicit symlink_handle(handle &&o) noexcept : handle(std::move(o)) + { + } + //! Move construction permitted + symlink_handle(symlink_handle &&) = default; + //! No copy construction (use `clone()`) + symlink_handle(const symlink_handle &) = delete; + //! Move assignment permitted + symlink_handle &operator=(symlink_handle &&) = default; + //! No copy assignment + symlink_handle &operator=(const symlink_handle &) = delete; + + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC ~symlink_handle() override + { + if(_v) + { + (void) symlink_handle::close(); + } + } + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> close() noexcept override + { + LLFIO_LOG_FUNCTION_CALL(this); + if(_flags & flag::unlink_on_first_close) + { + auto ret = unlink(); + if(!ret) + { + // File may have already been deleted, if so ignore + if(ret.error() != errc::no_such_file_or_directory) + { + return ret.error(); + } + } + } + return handle::close(); + } + + /*! 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. + */ + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result<symlink_handle> clone(mode mode_ = mode::unchanged, deadline d = std::chrono::seconds(30)) const noexcept; + +#if LLFIO_SYMLINK_HANDLE_IS_FAKED + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC + result<void> relink(const path_handle &base, path_view_type newpath, bool atomic_replace = true, deadline d = std::chrono::seconds(30)) noexcept override; + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC + result<void> unlink(deadline d = std::chrono::seconds(30)) noexcept override; +#endif + + /*! Create a symlink handle opening access to a symbolic link. + + For obvious reasons, one cannot append to a symbolic link, nor create with truncate. + */ + LLFIO_MAKE_FREE_FUNCTION + static LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result<symlink_handle> symlink(const path_handle &base, path_view_type path, mode _mode = mode::read, creation _creation = creation::open_existing, flag flags = flag::none) noexcept; + + /*! Read the contents of the symbolic link. + + If supplying your own `kernelbuffer`, be aware that the length of the contents of the symbolic + link may change at any time. You should therefore retry reading the symbolic link, expanding + your `kernelbuffer` each time, until a successful read occurs. + + \return Returns the buffers filled, with its size adjusted to the bytes filled. + \param tofill A buffer to fill with the contents of the symbolic link. + \param kernelbuffer A buffer to use for the kernel to fill. If left defaulted, a kernel buffer + is allocated internally and stored into `tofill` which needs to not be destructed until one + is no longer using any items within (the path returned is a view onto the original kernel data). + \errors Any of the errors which `readlink()` or `DeviceIoControl()` might return, or failure + to allocate memory if the user did not supply a kernel buffer to use. + \mallocs If the `kernelbuffer` parameter is set on entry, no memory allocations. + If unset, then at least one memory allocation, possibly more is performed. + */ + LLFIO_MAKE_FREE_FUNCTION + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result<buffers_type> read(io_request<buffers_type> req = {}) noexcept; + + /*! Write the contents of the symbolic link. + + \param req A buffer with which to replace the contents of the symbolic link. + \errors Any of the errors which `symlink()` or `DeviceIoControl()` might return. + \mallocs If the `kernelbuffer` parameter is set on entry, no memory allocations. + If unset, then at least one memory allocation, possibly more is performed. + */ + LLFIO_MAKE_FREE_FUNCTION + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result<const_buffers_type> write(io_request<const_buffers_type> req) noexcept; +}; + +//! \brief Constructor for `symlink_handle` +template <> struct construct<symlink_handle> +{ + const path_handle &base; + symlink_handle::path_view_type _path; + symlink_handle::mode _mode{symlink_handle::mode::read}; + symlink_handle::creation _creation{symlink_handle::creation::open_existing}; + result<symlink_handle> operator()() const noexcept { return symlink_handle::symlink(base, _path, _mode, _creation); } +}; + + +LLFIO_V2_NAMESPACE_END + +#if LLFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS) +#define LLFIO_INCLUDED_BY_HEADER 1 +#ifdef _WIN32 +#include "detail/impl/windows/symlink_handle.ipp" +#else +#include "detail/impl/posix/symlink_handle.ipp" +#endif +#undef LLFIO_INCLUDED_BY_HEADER +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif diff --git a/include/llfio/v2.0/utils.hpp b/include/llfio/v2.0/utils.hpp index 8d731016..0de1930e 100644 --- a/include/llfio/v2.0/utils.hpp +++ b/include/llfio/v2.0/utils.hpp @@ -68,8 +68,7 @@ namespace utils template <class T> inline T round_to_page_size(T i) noexcept { const size_t pagesize = page_size(); - i.data = reinterpret_cast<byte *>((LLFIO_V2_NAMESPACE::detail::unsigned_integer_cast<uintptr_t>(i.data)) & ~(pagesize - 1)); - i.len = (i.len + pagesize - 1) & ~(pagesize - 1); + i = {reinterpret_cast<byte *>((LLFIO_V2_NAMESPACE::detail::unsigned_integer_cast<uintptr_t>(i.data())) & ~(pagesize - 1)), (i.size() + pagesize - 1) & ~(pagesize - 1)}; return i; } diff --git a/release_notes.md b/release_notes.md index 590013f4..0e1b136b 100644 --- a/release_notes.md +++ b/release_notes.md @@ -146,18 +146,13 @@ Todo: | | ✔ | ✔ | Set random or sequential i/o (prefetch). | ✔ | ✔ | ✔ | i/o on `async_file_handle` is coroutines awaitable. | ✔ | ✔ | | `llfio::algorithm::trivial_vector<T>` with constant time reallocation if `T` is trivially copyable. - -Todo to reach feature parity with LLFIO v1: - -| NEW in v2 | Windows | POSIX | | -| --------- | --------| ----- | --- | -| | | | `symlink_handle`. -| | | | BSD and OS X kqueues optimised `io_service` +| | ✔ | ✔ | `symlink_handle`. Todo thereafter in order of priority: | NEW in v2 | Windows | POSIX | | | --------- | --------| ----- | --- | +| ✔ | | | A mechanism for writing a `stat_t` onto an inode. | ✔ | | | Page allocator based on an index of linked list of free pages. See notes. | ✔ | | | Optionally concurrent B+ tree index based on page allocator for key-value store. | ✔ | | | Attributes extending `span<buffers_type>` with DMA colouring. diff --git a/test/tests/path_view.cpp b/test/tests/path_view.cpp index 56307985..5648e8e2 100644 --- a/test/tests/path_view.cpp +++ b/test/tests/path_view.cpp @@ -24,6 +24,32 @@ Distributed under the Boost Software License, Version 1.0. #include "../test_kernel_decl.hpp" +template <class U> inline void CheckPathView(const LLFIO_V2_NAMESPACE::filesystem::path &p, const char *desc, U &&c) +{ + using LLFIO_V2_NAMESPACE::filesystem::path; + using LLFIO_V2_NAMESPACE::path_view; + auto r1 = c(p); + auto r2 = c(path_view(p)); + BOOST_CHECK(r1 == r2); + if(r1 != r2) + { + std::cerr << "For " << desc << " with path " << p << "\n"; + std::cerr << " filesystem::path returned " << r1 << "\n"; + std::cerr << " path_view returned " << r2 << std::endl; + } +} + +static inline void CheckPathView(const LLFIO_V2_NAMESPACE::filesystem::path &path) +{ + CheckPathView(path, "root_directory()", [](const auto &p) { return p.root_directory(); }); + CheckPathView(path, "root_path()", [](const auto &p) { return p.root_path(); }); + CheckPathView(path, "relative_path()", [](const auto &p) { return p.relative_path(); }); + CheckPathView(path, "parent_path()", [](const auto &p) { return p.parent_path(); }); + CheckPathView(path, "filename()", [](const auto &p) { return p.filename(); }); + CheckPathView(path, "stem()", [](const auto &p) { return p.stem(); }); + CheckPathView(path, "extension()", [](const auto &p) { return p.extension(); }); +} + static inline void TestPathView() { namespace llfio = LLFIO_V2_NAMESPACE; @@ -48,6 +74,33 @@ static inline void TestPathView() llfio::path_view::c_str h(f); BOOST_CHECK(h.buffer == p + 70); // NOLINT #endif + CheckPathView("/mnt/c/Users/ned/Documents/boostish/afio/programs/build_posix/testdir"); +#if 0 + // I think we are standards conforming here, Dinkumware and libstdc++ are not + CheckPathView("/mnt/c/Users/ned/Documents/boostish/afio/programs/build_posix/testdir/"); +#endif + CheckPathView("/mnt/c/Users/ned/Documents/boostish/afio/programs/build_posix/testdir/0"); + CheckPathView("/mnt/c/Users/ned/Documents/boostish/afio/programs/build_posix/testdir/0.txt"); + CheckPathView("boostish/afio/programs/build_posix/testdir"); +#if 0 + // I think we are standards conforming here, Dinkumware and libstdc++ are not + CheckPathView("boostish/afio/programs/build_posix/testdir/"); +#endif + CheckPathView("boostish/afio/programs/build_posix/testdir/0"); + CheckPathView("boostish/afio/programs/build_posix/testdir/0.txt"); + CheckPathView("0"); + CheckPathView("0.txt"); + CheckPathView("0.foo.txt"); + CheckPathView(".0.foo.txt"); +#if 0 + // I think we are standards conforming here, Dinkumware and libstdc++ are not + CheckPathView(".txt"); + CheckPathView("/"); + CheckPathView("//"); +#endif + CheckPathView(""); + CheckPathView("."); + CheckPathView(".."); #ifdef _WIN32 // On Windows, UTF-8 and UTF-16 paths are equivalent and backslash conversion happens @@ -72,6 +125,26 @@ static inline void TestPathView() BOOST_CHECK(j.buffer == p2); llfio::path_view::c_str k(h, false); BOOST_CHECK(k.buffer == p2 + 70); + + CheckPathView(L"\\mnt\\c\\Users\\ned\\Documents\\boostish\\afio\\programs\\build_posix\\testdir\\0"); + CheckPathView(L"C:\\Users\\ned\\Documents\\boostish\\afio\\programs\\build_posix\\testdir\\0"); + CheckPathView("C:/Users/ned/Documents/boostish/afio/programs/build_posix/testdir/0.txt"); + // CheckPathView(L"\\\\niall\\douglas.txt"); + CheckPathView(L"\\!!\\niall\\douglas.txt"); + CheckPathView(L"\\??\\niall\\douglas.txt"); + CheckPathView(L"\\\\?\\niall\\douglas.txt"); + CheckPathView(L"\\\\.\\niall\\douglas.txt"); + + // Handle NT kernel paths correctly + BOOST_CHECK(llfio::path_view(L"\\\\niall").is_absolute()); + BOOST_CHECK(llfio::path_view(L"\\!!\\niall").is_absolute()); + BOOST_CHECK(llfio::path_view(L"\\??\\niall").is_absolute()); + BOOST_CHECK(llfio::path_view(L"\\\\?\\niall").is_absolute()); + BOOST_CHECK(llfio::path_view(L"\\\\.\\niall").is_absolute()); + // On Windows this is relative, on POSIX it is absolute + BOOST_CHECK(llfio::path_view("/niall").is_relative()); +#else + BOOST_CHECK(llfio::path_view("/niall").is_absolute()); #endif } diff --git a/test/tests/symlink_handle_create_close/existing0/testfile.txt b/test/tests/symlink_handle_create_close/existing0/testfile.txt new file mode 100644 index 00000000..e55f0f57 --- /dev/null +++ b/test/tests/symlink_handle_create_close/existing0/testfile.txt @@ -0,0 +1 @@ +niall
\ No newline at end of file diff --git a/test/tests/symlink_handle_create_close/existing1/testfile.txt b/test/tests/symlink_handle_create_close/existing1/testfile.txt new file mode 100644 index 00000000..e55f0f57 --- /dev/null +++ b/test/tests/symlink_handle_create_close/existing1/testfile.txt @@ -0,0 +1 @@ +niall
\ No newline at end of file diff --git a/test/tests/symlink_handle_create_close/existing1/testlink b/test/tests/symlink_handle_create_close/existing1/testlink new file mode 120000 index 00000000..62c74bcb --- /dev/null +++ b/test/tests/symlink_handle_create_close/existing1/testlink @@ -0,0 +1 @@ +testfile.txt
\ No newline at end of file diff --git a/test/tests/symlink_handle_create_close/kernel_symlink_handle.cpp.hpp b/test/tests/symlink_handle_create_close/kernel_symlink_handle.cpp.hpp new file mode 100644 index 00000000..04cb9098 --- /dev/null +++ b/test/tests/symlink_handle_create_close/kernel_symlink_handle.cpp.hpp @@ -0,0 +1,57 @@ +/* Test kernel for symlink_handle create and close +(C) 2018 Niall Douglas <http://www.nedproductions.biz/> (5 commits) +File Created: Jul 2018 + + +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" + +namespace symlink_handle_create_close +{ + LLFIO_TEST_KERNEL_DECL LLFIO_V2_NAMESPACE::result<LLFIO_V2_NAMESPACE::symlink_handle> test_kernel_symlink_handle_absolute(LLFIO_V2_NAMESPACE::symlink_handle::mode m, LLFIO_V2_NAMESPACE::symlink_handle::creation c, LLFIO_V2_NAMESPACE::symlink_handle::flag f) + { + auto h = LLFIO_V2_NAMESPACE::symlink_handle::symlink({}, "testlink", m, c, f); + if(h) + { + if(h.value().is_writable()) + { + h.value().write("testfile.txt").value(); + } + h.value().close().value(); + } + return h; + } + LLFIO_TEST_KERNEL_DECL LLFIO_V2_NAMESPACE::result<LLFIO_V2_NAMESPACE::symlink_handle> test_kernel_symlink_handle_relative(LLFIO_V2_NAMESPACE::symlink_handle::mode m, LLFIO_V2_NAMESPACE::symlink_handle::creation c, LLFIO_V2_NAMESPACE::symlink_handle::flag f) + { + OUTCOME_TRY(b, LLFIO_V2_NAMESPACE::path_handle::path(".")); + auto h = LLFIO_V2_NAMESPACE::symlink_handle::symlink(b, "testlink", m, c, f); + if(h) + { + if(h.value().is_writable()) + { + h.value().write("testfile.txt").value(); + } + h.value().close().value(); + } + b.close().value(); + return h; + } +} // namespace file_handle_create_close diff --git a/test/tests/symlink_handle_create_close/runner.cpp b/test/tests/symlink_handle_create_close/runner.cpp new file mode 100644 index 00000000..1e14f1a0 --- /dev/null +++ b/test/tests/symlink_handle_create_close/runner.cpp @@ -0,0 +1,83 @@ +/* Integration test kernel for symlink_handle create and close +(C) 2018 Niall Douglas <http://www.nedproductions.biz/> (22 commits) +File Created: Jul 2018 + + +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 "kernel_symlink_handle.cpp.hpp" + +template <class U> inline void symlink_handle_create_close_creation(U &&f) +{ + using namespace KERNELTEST_V1_NAMESPACE; + using LLFIO_V2_NAMESPACE::result; + using symlink_handle = LLFIO_V2_NAMESPACE::symlink_handle; + static const result<void> no_such_file_or_directory = LLFIO_V2_NAMESPACE::errc::no_such_file_or_directory; + static const result<void> file_exists = LLFIO_V2_NAMESPACE::errc::file_exists; + static const result<void> function_not_supported = LLFIO_V2_NAMESPACE::errc::function_not_supported; + static const result<void> permission_denied = LLFIO_V2_NAMESPACE::errc::permission_denied; + + // clang-format off + static const auto permuter(mt_permute_parameters< + result<void>, + parameters< + typename symlink_handle::mode, + typename symlink_handle::creation, + typename symlink_handle::flag + >, + precondition::filesystem_setup_parameters, + postcondition::filesystem_comparison_structure_parameters + >( + { + + // Does the mode parameter have the expected side effects? + { success(), { symlink_handle::mode::none, symlink_handle::creation::if_needed, symlink_handle::flag::none }, { "existing1" }, { "existing1" }}, + { success(), { symlink_handle::mode::attr_read, symlink_handle::creation::if_needed, symlink_handle::flag::none }, { "existing1" }, { "existing1" }}, + { success(), { symlink_handle::mode::attr_write, symlink_handle::creation::if_needed, symlink_handle::flag::none }, { "existing1" }, { "existing1" }}, + { success(), { symlink_handle::mode::write, symlink_handle::creation::if_needed, symlink_handle::flag::none }, { "existing0" }, { "existing1" }}, + { success(), { symlink_handle::mode::write, symlink_handle::creation::if_needed, symlink_handle::flag::none }, { "existing1" }, { "existing1" }}, + { function_not_supported, { symlink_handle::mode::append, symlink_handle::creation::if_needed, symlink_handle::flag::none }, { "existing1" }, { "existing1" }}, + { success(), { symlink_handle::mode::none, symlink_handle::creation::if_needed, symlink_handle::flag::none }, { "existing0" }, { "existing1" }}, + { success(), { symlink_handle::mode::attr_read, symlink_handle::creation::if_needed, symlink_handle::flag::none }, { "existing0" }, { "existing1" }}, + { success(), { symlink_handle::mode::attr_write, symlink_handle::creation::if_needed, symlink_handle::flag::none }, { "existing0" }, { "existing1" }}, + + // Does the creation parameter have the expected side effects? + { no_such_file_or_directory, { symlink_handle::mode::write, symlink_handle::creation::open_existing , symlink_handle::flag::none }, { "existing0" }, { "existing0" }}, + { success(), { symlink_handle::mode::write, symlink_handle::creation::open_existing , symlink_handle::flag::none }, { "existing1" }, { "existing1" }}, + { success(), { symlink_handle::mode::write, symlink_handle::creation::only_if_not_exist, symlink_handle::flag::none }, { "existing0" }, { "existing1" }}, + { file_exists, { symlink_handle::mode::write, symlink_handle::creation::only_if_not_exist, symlink_handle::flag::none }, { "existing1" }, { "existing1" }}, + { success(), { symlink_handle::mode::write, symlink_handle::creation::if_needed , symlink_handle::flag::none }, { "existing0" }, { "existing1" }}, + { success(), { symlink_handle::mode::write, symlink_handle::creation::if_needed , symlink_handle::flag::none }, { "existing1" }, { "existing1" }}, + { function_not_supported, { symlink_handle::mode::write, symlink_handle::creation::truncate , symlink_handle::flag::none }, { "existing1" }, { "existing1" }}, + + // Does the flag parameter have the expected side effects? + { success(), { symlink_handle::mode::write, symlink_handle::creation::open_existing, symlink_handle::flag::unlink_on_first_close }, { "existing1" }, { "existing0" }} + }, + precondition::filesystem_setup(), + postcondition::filesystem_comparison_structure() + )); + // clang-format on + + auto results = permuter(std::forward<U>(f)); + check_results_with_boost_test(permuter, results); +} + +KERNELTEST_TEST_KERNEL(unit, llfio, symlink_handle_create_close, symlink_handle, "Tests that llfio::symlink_handle::symlink()'s parameters with absolute paths work as expected", symlink_handle_create_close_creation(symlink_handle_create_close::test_kernel_symlink_handle_absolute)) +KERNELTEST_TEST_KERNEL(unit, llfio, symlink_handle_create_close, symlink_handle, "Tests that llfio::symlink_handle::symlink()'s parameters with relative paths work as expected", symlink_handle_create_close_creation(symlink_handle_create_close::test_kernel_symlink_handle_relative)) |