diff options
Diffstat (limited to 'include/llfio/v2.0')
-rw-r--r-- | include/llfio/v2.0/deadline.h | 17 | ||||
-rw-r--r-- | include/llfio/v2.0/detail/impl/posix/fs_handle.ipp | 144 | ||||
-rw-r--r-- | include/llfio/v2.0/detail/impl/posix/stat.ipp | 12 | ||||
-rw-r--r-- | include/llfio/v2.0/detail/impl/posix/symlink_handle.ipp | 396 | ||||
-rw-r--r-- | include/llfio/v2.0/detail/impl/windows/fs_handle.ipp | 2 | ||||
-rw-r--r-- | include/llfio/v2.0/detail/impl/windows/import.hpp | 15 | ||||
-rw-r--r-- | include/llfio/v2.0/detail/impl/windows/io_handle.ipp | 4 | ||||
-rw-r--r-- | include/llfio/v2.0/detail/impl/windows/symlink_handle.ipp | 2 | ||||
-rw-r--r-- | include/llfio/v2.0/fs_handle.hpp | 5 | ||||
-rw-r--r-- | include/llfio/v2.0/path_handle.hpp | 10 | ||||
-rw-r--r-- | include/llfio/v2.0/status_code.hpp | 36 | ||||
-rw-r--r-- | include/llfio/v2.0/symlink_handle.hpp | 103 |
12 files changed, 620 insertions, 126 deletions
diff --git a/include/llfio/v2.0/deadline.h b/include/llfio/v2.0/deadline.h index be6e14d6..c7681d52 100644 --- a/include/llfio/v2.0/deadline.h +++ b/include/llfio/v2.0/deadline.h @@ -104,6 +104,23 @@ struct LLFIO_DEADLINE_NAME #endif }; +#define LLFIO_DEADLINE_TO_PARTIAL_DEADLINE(nd, d) \ + if(d) \ + { \ + if((d).steady) \ + { \ + (nd).steady = true; \ + std::chrono::nanoseconds ns = std::chrono::duration_cast<std::chrono::nanoseconds>((began_steady + std::chrono::nanoseconds((d).nsecs)) - std::chrono::steady_clock::now()); \ + if(ns.count() < 0) \ + (nd).nsecs = 0; \ + else \ + (nd).nsecs = ns.count(); \ + } \ + else \ + (nd) = (d); \ + } + + #undef LLFIO_DEADLINE_NAME #if defined(__cplusplus) || DOXYGEN_IS_IN_THE_HOUSE LLFIO_V2_NAMESPACE_END diff --git a/include/llfio/v2.0/detail/impl/posix/fs_handle.ipp b/include/llfio/v2.0/detail/impl/posix/fs_handle.ipp index 7a89910b..9457e55b 100644 --- a/include/llfio/v2.0/detail/impl/posix/fs_handle.ipp +++ b/include/llfio/v2.0/detail/impl/posix/fs_handle.ipp @@ -40,104 +40,96 @@ result<void> fs_handle::_fetch_inode() const noexcept return success(); } -inline result<path_handle> containing_directory(optional<std::reference_wrapper<filesystem::path>> out_filename, const handle &h, const fs_handle &fsh, deadline d) noexcept +namespace detail { - std::chrono::steady_clock::time_point began_steady; - std::chrono::system_clock::time_point end_utc; - if(d) + result<path_handle> containing_directory(optional<std::reference_wrapper<filesystem::path>> out_filename, const handle &h, const fs_handle &fsh, deadline d) noexcept { - if(d.steady) + std::chrono::steady_clock::time_point began_steady; + std::chrono::system_clock::time_point end_utc; + if(d) { - began_steady = std::chrono::steady_clock::now(); - } - else - { - end_utc = d.to_time_point(); - } - } - try - { - for(;;) - { - // Get current path for handle and open its containing dir - OUTCOME_TRY(_currentpath, h.current_path()); - // If current path is empty, it's been deleted - if(_currentpath.empty()) + if(d.steady) { - return errc::no_such_file_or_directory; + began_steady = std::chrono::steady_clock::now(); } - // Split the path into root and leafname - path_view currentpath(_currentpath); - path_view filename = currentpath.filename(); - currentpath.remove_filename(); - // Zero terminate the root path so it doesn't get copied later - const_cast<filesystem::path::string_type &>(_currentpath.native())[currentpath.native_size()] = 0; - auto currentdirh_ = path_handle::path(currentpath); - if(!currentdirh_) + else { - continue; + end_utc = d.to_time_point(); } - path_handle currentdirh = std::move(currentdirh_.value()); - if(h.flags() & handle::flag::disable_safety_unlinks) + } + try + { + for(;;) { - if(out_filename) + // Get current path for handle and open its containing dir + OUTCOME_TRY(_currentpath, h.current_path()); + // If current path is empty, it's been deleted + if(_currentpath.empty()) { - out_filename->get() = filename.path(); + return errc::no_such_file_or_directory; } - return success(std::move(currentdirh)); - } - // Open the same file name, and compare dev and inode - path_view::c_str zpath(filename); - int fd = ::openat(currentdirh.native_handle().fd, zpath.buffer, O_CLOEXEC); - if(fd == -1) - { - if(ENOENT == errno) + // Split the path into root and leafname + path_view currentpath(_currentpath); + path_view filename = currentpath.filename(); + currentpath.remove_filename(); + // Zero terminate the root path so it doesn't get copied later + const_cast<filesystem::path::string_type &>(_currentpath.native())[currentpath.native_size()] = 0; + auto currentdirh_ = path_handle::path(currentpath); + if(!currentdirh_) { continue; } - return posix_error(); - } - auto unfd = undoer([fd] { ::close(fd); }); - (void) unfd; - struct stat s - { - }; - if(-1 == ::fstat(fd, &s)) - { - continue; - } - // If the same, we know for a fact that this is the correct containing dir for now at least - if(static_cast<fs_handle::dev_t>(s.st_dev) == fsh.st_dev() && s.st_ino == fsh.st_ino()) - { - if(out_filename) + path_handle currentdirh = std::move(currentdirh_.value()); + if((h.flags() & handle::flag::disable_safety_unlinks) != 0) { - out_filename->get() = filename.path(); + if(out_filename) + { + out_filename->get() = filename.path(); + } + return success(std::move(currentdirh)); } - return success(std::move(currentdirh)); - } - // Check timeout - if(d) - { - if(d.steady) + // stat the same file name, and compare dev and inode + path_view::c_str zpath(filename); + struct stat s + { + }; + if(-1 == ::fstatat(currentdirh.native_handle().fd, zpath.buffer, &s, AT_SYMLINK_NOFOLLOW)) + { + continue; + } + // If the same, we know for a fact that this is the correct containing dir for now at least + if(static_cast<fs_handle::dev_t>(s.st_dev) == fsh.st_dev() && s.st_ino == fsh.st_ino()) { - if(std::chrono::steady_clock::now() >= (began_steady + std::chrono::nanoseconds(d.nsecs))) + if(out_filename) { - return errc::timed_out; + out_filename->get() = filename.path(); } + return success(std::move(currentdirh)); } - else + // Check timeout + if(d) { - if(std::chrono::system_clock::now() >= end_utc) + if(d.steady) + { + if(std::chrono::steady_clock::now() >= (began_steady + std::chrono::nanoseconds(d.nsecs))) + { + return errc::timed_out; + } + } + else { - return errc::timed_out; + if(std::chrono::system_clock::now() >= end_utc) + { + return errc::timed_out; + } } } } } - } - catch(...) - { - return error_from_exception(); + catch(...) + { + return error_from_exception(); + } } } @@ -149,7 +141,7 @@ result<path_handle> fs_handle::parent_path_handle(deadline d) const noexcept { OUTCOME_TRY(_fetch_inode()); } - return containing_directory({}, h, *this, d); + return detail::containing_directory({}, h, *this, d); } result<void> fs_handle::relink(const path_handle &base, path_view_type path, bool atomic_replace, deadline d) noexcept @@ -181,7 +173,7 @@ result<void> fs_handle::relink(const path_handle &base, path_view_type path, boo { OUTCOME_TRY(_fetch_inode()); } - OUTCOME_TRY(dirh, containing_directory(std::ref(filename), h, *this, d)); + OUTCOME_TRY(dirh, detail::containing_directory(std::ref(filename), h, *this, d)); if(!atomic_replace) { // Some systems provide an extension for atomic non-replacing renames @@ -214,7 +206,7 @@ result<void> fs_handle::unlink(deadline d) noexcept { OUTCOME_TRY(_fetch_inode()); } - OUTCOME_TRY(dirh, containing_directory(std::ref(filename), h, *this, d)); + OUTCOME_TRY(dirh, detail::containing_directory(std::ref(filename), h, *this, d)); if(-1 == ::unlinkat(dirh.native_handle().fd, filename.c_str(), h.is_directory() ? AT_REMOVEDIR : 0)) { return posix_error(); diff --git a/include/llfio/v2.0/detail/impl/posix/stat.ipp b/include/llfio/v2.0/detail/impl/posix/stat.ipp index 58a8e76b..28eb3ccb 100644 --- a/include/llfio/v2.0/detail/impl/posix/stat.ipp +++ b/include/llfio/v2.0/detail/impl/posix/stat.ipp @@ -29,6 +29,11 @@ Distributed under the Boost Software License, Version 1.0. LLFIO_V2_NAMESPACE_BEGIN +namespace detail +{ + LLFIO_HEADERS_ONLY_FUNC_SPEC result<void> stat_from_symlink(struct stat &s, const handle &h) noexcept; +} + static inline filesystem::file_type to_st_type(uint16_t mode) { switch(mode & S_IFMT) @@ -74,7 +79,12 @@ LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result<size_t> stat_t::fill(const handle &h, sta if(-1 == ::fstat(h.native_handle().fd, &s)) { - return posix_error(); + if(!h.is_symlink() || EBADF != errno) + { + return posix_error(); + } + // This is a hack, but symlink_handle includes this first so there is a chicken and egg dependency problem + OUTCOME_TRY(detail::stat_from_symlink(s, h)); } if(wanted & want::dev) { diff --git a/include/llfio/v2.0/detail/impl/posix/symlink_handle.ipp b/include/llfio/v2.0/detail/impl/posix/symlink_handle.ipp new file mode 100644 index 00000000..a0d4e7cd --- /dev/null +++ b/include/llfio/v2.0/detail/impl/posix/symlink_handle.ipp @@ -0,0 +1,396 @@ +/* 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 + +namespace detail +{ + // Used by stat_t.cpp to work around a dependency problem + LLFIO_HEADERS_ONLY_FUNC_SPEC result<void> stat_from_symlink(struct stat &s, const handle &_h) noexcept + { +#if !LLFIO_SYMLINK_HANDLE_IS_FAKED + return posix_error(EBADF); +#else + const auto &h = static_cast<const symlink_handle &>(_h); + if(-1 == ::fstatat(h._dirh.native_handle().fd, h._leafname.c_str(), &s, AT_SYMLINK_NOFOLLOW)) + { + return posix_error(); + } + return success(); +#endif + } +} + +result<void> symlink_handle::_create_symlink(path_view target, deadline d, bool atomic_replace) noexcept +{ + std::chrono::steady_clock::time_point began_steady; + std::chrono::system_clock::time_point end_utc; + if(d) + { + if(d.steady) + { + began_steady = std::chrono::steady_clock::now(); + } + else + { + end_utc = d.to_time_point(); + } + } + path_view::c_str zpath(target); + try + { +#if !LLFIO_SYMLINK_HANDLE_IS_FAKED + if(_devid == 0 && _inode == 0) + { + OUTCOME_TRY(_fetch_inode()); + } + path_type filename; + OUTCOME_TRY(dirh, detail::containing_directory(std::ref(filename), *this, *this, d)); +#else + const path_handle &dirh = _dirh; + const path_type &filename = _leafname; +#endif + if(atomic_replace) + { + // symlinkat() won't replace an existing symlink, so we need to create it + // with a random name and atomically rename over the existing one. + for(;;) + { + auto randomname = utils::random_string(32); + randomname.append(".random"); + if(-1 == ::symlinkat(zpath.buffer, dirh.native_handle().fd, randomname.c_str())) + { + if(EEXIST == errno) + { + // Check timeout + if(d) + { + if(d.steady) + { + if(std::chrono::steady_clock::now() >= (began_steady + std::chrono::nanoseconds(d.nsecs))) + { + return errc::timed_out; + } + } + else + { + if(std::chrono::system_clock::now() >= end_utc) + { + return errc::timed_out; + } + } + } + continue; + } + return posix_error(); + } + if(-1 == ::renameat(dirh.native_handle().fd, randomname.c_str(), dirh.native_handle().fd, filename.c_str())) + { + return posix_error(); + } + return success(); + } + } + else + { + if(-1 == ::symlinkat(zpath.buffer, dirh.native_handle().fd, filename.c_str())) + { + return posix_error(); + } + return success(); + } + } + catch(...) + { + return error_from_exception(); + } +} + +result<symlink_handle> symlink_handle::clone(mode mode_, deadline d) const noexcept +{ + LLFIO_LOG_FUNCTION_CALL(this); +#if LLFIO_SYMLINK_HANDLE_IS_FAKED + result<symlink_handle> ret(symlink_handle(native_handle_type(), _devid, _inode, _flags)); + ret.value()._v.behaviour = _v.behaviour; + OUTCOME_TRY(dirh, _dirh.clone()); + ret.value()._dirh = std::move(dirh); + try + { + ret.value()._leafname = _leafname; + } + catch(...) + { + return error_from_exception(); + } + return ret; +#endif + // fast path + if(mode_ == mode::unchanged) + { + result<symlink_handle> ret(symlink_handle(native_handle_type(), _devid, _inode, _flags)); + ret.value()._v.behaviour = _v.behaviour; + ret.value()._v.fd = ::fcntl(_v.fd, F_DUPFD_CLOEXEC); + if(-1 == ret.value()._v.fd) + { + return posix_error(); + } + return ret; + } + // Slow path + std::chrono::steady_clock::time_point began_steady; + std::chrono::system_clock::time_point end_utc; + if(d) + { + if(d.steady) + { + began_steady = std::chrono::steady_clock::now(); + } + else + { + end_utc = d.to_time_point(); + } + } + for(;;) + { + // Get the current path of myself + OUTCOME_TRY(currentpath, current_path()); + // Open myself + auto fh = symlink({}, currentpath, mode_, creation::open_existing, _flags); + if(fh) + { + if(fh.value().unique_id() == unique_id()) + { + return fh; + } + } + else + { + if(fh.error() != errc::no_such_file_or_directory) + { + return fh.error(); + } + } + // Check timeout + if(d) + { + if(d.steady) + { + if(std::chrono::steady_clock::now() >= (began_steady + std::chrono::nanoseconds(d.nsecs))) + { + return errc::timed_out; + } + } + else + { + if(std::chrono::system_clock::now() >= end_utc) + { + return errc::timed_out; + } + } + } + } +} + +#if LLFIO_SYMLINK_HANDLE_IS_FAKED +result<symlink_handle::path_type> symlink_handle::current_path() const noexcept +{ + LLFIO_LOG_FUNCTION_CALL(this); + try + { + for(;;) + { + // Sanity check that we still exist + struct stat s1, s2; + if(-1 == ::fstatat(_dirh.native_handle().fd, _leafname.c_str(), &s1, AT_SYMLINK_NOFOLLOW)) + { + return posix_error(); + } + OUTCOME_TRY(dirpath, _dirh.current_path()); + dirpath /= _leafname; + if(-1 == ::lstat(dirpath.c_str(), &s2) || s1.st_dev != s2.st_dev || s1.st_ino != s2.st_ino) + { + continue; + } + return dirpath; + } + } + catch(...) + { + return error_from_exception(); + } +} +#endif + +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 +{ + 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; + if(_mode == mode::append || _creation == creation::truncate) + { + return errc::function_not_supported; + } + OUTCOME_TRY(attribs, attribs_from_handle_mode_caching_and_flags(nativeh, _mode, _creation, caching::all, flags)); + nativeh.behaviour &= ~native_handle_type::disposition::seekable; // not seekable +#if !LLFIO_SYMLINK_HANDLE_IS_FAKED + // Linux can open symbolic links directly like this + attribs |= O_PATH | O_NOFOLLOW; + path_view::c_str zpath(path); + if(base.is_valid()) + { + nativeh.fd = ::openat(base.native_handle().fd, zpath.buffer, attribs, 0x1b0 /*660*/); + } + else + { + nativeh.fd = ::open(zpath.buffer, attribs, 0x1b0 /*660*/); + } + if(-1 == nativeh.fd) + { + return posix_error(); + } +#else + (void) attribs; + try + { + // Take a path handle to the directory containing the symlink + auto path_parent = path.parent_path(); + ret.value()._leafname = path.filename().path(); + if(base.is_valid() && !path_parent.empty()) + { + OUTCOME_TRY(dh, base.clone()); + ret.value()._dirh = std::move(dh); + } + else + { + OUTCOME_TRY(dh, path_handle::path(base, path_parent.empty() ? "." : path_parent)); + ret.value()._dirh = std::move(dh); + } + } + catch(...) + { + return error_from_exception(); + } + switch(_creation) + { + case creation::open_existing: + { + // Complain if it doesn't exist + struct stat s; + if(-1 == ::fstatat(ret.value()._dirh.native_handle().fd, ret.value()._leafname.c_str(), &s, AT_SYMLINK_NOFOLLOW)) + { + return posix_error(); + } + break; + } + case creation::only_if_not_exist: + case creation::if_needed: + case creation::truncate: + { + // Create an empty symlink, ignoring any file exists errors, unless only_if_not_exist + auto r = ret.value()._create_symlink( +#ifdef __linux__ + ".", // Linux is not POSIX conforming here, and refuses to create empty symlinks +#else + "", +#endif + std::chrono::seconds(10), false); + if(!r) + { + if(_creation == creation::only_if_not_exist || r.error() != errc::file_exists) + return r.error(); + } + break; + } + } +#endif + return ret; +} + +result<symlink_handle::buffers_type> symlink_handle::read(symlink_handle::io_request<symlink_handle::buffers_type> req) noexcept +{ + LLFIO_LOG_FUNCTION_CALL(this); + symlink_handle::buffers_type tofill; + if(req.kernelbuffer.empty()) + { + // Let's assume the average symbolic link will be 256 characters long. + size_t toallocate = 256; + 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; + } + for(;;) + { + char *buffer = req.kernelbuffer.empty() ? tofill._kernel_buffer.get() : req.kernelbuffer.data(); + size_t bytes = req.kernelbuffer.empty() ? tofill._kernel_buffer_size : req.kernelbuffer.size(); +#if !LLFIO_SYMLINK_HANDLE_IS_FAKED + // Linux has the ability to read the link from a fd + ssize_t read = ::readlinkat(_v.fd, "", buffer, bytes); +#else + ssize_t read = ::readlinkat(_dirh.native_handle().fd, _leafname.c_str(), buffer, bytes); +#endif + if(read == -1) + { + return posix_error(); + } + if((size_t) read == bytes) + { + if(req.kernelbuffer.empty()) + { + 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 errc::not_enough_memory; + } + // We know we can null terminate as read < bytes + buffer[read] = 0; + tofill._link = path_view(buffer, read); + tofill._type = symlink_type::symbolic; + return std::move(tofill); + } +} + +result<symlink_handle::const_buffers_type> symlink_handle::write(symlink_handle::io_request<symlink_handle::const_buffers_type> req, deadline d) noexcept +{ + LLFIO_LOG_FUNCTION_CALL(this); + OUTCOME_TRY(_create_symlink(req.buffers.path(), d, true)); + return success(std::move(req.buffers)); +} + +LLFIO_V2_NAMESPACE_END 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 2b440326..e42283e3 100644 --- a/include/llfio/v2.0/detail/impl/windows/fs_handle.ipp +++ b/include/llfio/v2.0/detail/impl/windows/fs_handle.ipp @@ -73,7 +73,7 @@ result<path_handle> fs_handle::parent_path_handle(deadline d) const noexcept continue; } path_handle currentdirh = std::move(currentdirh_.value()); - if(h.flags() & handle::flag::disable_safety_unlinks) + if((h.flags() & handle::flag::disable_safety_unlinks) != 0 || h.is_symlink()) { return success(std::move(currentdirh)); } diff --git a/include/llfio/v2.0/detail/impl/windows/import.hpp b/include/llfio/v2.0/detail/impl/windows/import.hpp index 0c739dbd..c7ccf165 100644 --- a/include/llfio/v2.0/detail/impl/windows/import.hpp +++ b/include/llfio/v2.0/detail/impl/windows/import.hpp @@ -1060,21 +1060,6 @@ if(d) _timeout.QuadPart = ns.count() / -100; \ } -#define LLFIO_WIN_DEADLINE_TO_PARTIAL_DEADLINE(nd, d) \ - if(d) \ - { \ - if((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()); \ - if(ns.count() < 0) \ - (nd).nsecs = 0; \ - else \ - (nd).nsecs = ns.count(); \ - } \ - else \ - (nd) = (d); \ - } - #define LLFIO_WIN_DEADLINE_TO_TIMEOUT(d) \ \ if(d) \ 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 1c61ea2d..d1ec4e34 100644 --- a/include/llfio/v2.0/detail/impl/windows/io_handle.ipp +++ b/include/llfio/v2.0/detail/impl/windows/io_handle.ipp @@ -99,8 +99,8 @@ template <class BuffersType, class Syscall> inline io_handle::io_result<BuffersT { for(auto &ol : ols) { - deadline nd = d; - LLFIO_WIN_DEADLINE_TO_PARTIAL_DEADLINE(nd, d); + deadline nd; + LLFIO_DEADLINE_TO_PARTIAL_DEADLINE(nd, d); if(STATUS_TIMEOUT == ntwait(nativeh.h, ol, nd)) { LLFIO_WIN_DEADLINE_TO_TIMEOUT(d); diff --git a/include/llfio/v2.0/detail/impl/windows/symlink_handle.ipp b/include/llfio/v2.0/detail/impl/windows/symlink_handle.ipp index f9138f78..63097a07 100644 --- a/include/llfio/v2.0/detail/impl/windows/symlink_handle.ipp +++ b/include/llfio/v2.0/detail/impl/windows/symlink_handle.ipp @@ -196,7 +196,7 @@ result<symlink_handle::buffers_type> symlink_handle::read(symlink_handle::io_req } } -result<symlink_handle::const_buffers_type> symlink_handle::write(symlink_handle::io_request<symlink_handle::const_buffers_type> req) noexcept +result<symlink_handle::const_buffers_type> symlink_handle::write(symlink_handle::io_request<symlink_handle::const_buffers_type> req, deadline /*unused*/) noexcept { windows_nt_kernel::init(); using namespace windows_nt_kernel; diff --git a/include/llfio/v2.0/fs_handle.hpp b/include/llfio/v2.0/fs_handle.hpp index d4e1619d..ed4ef1f2 100644 --- a/include/llfio/v2.0/fs_handle.hpp +++ b/include/llfio/v2.0/fs_handle.hpp @@ -195,6 +195,11 @@ public: result<void> unlink(deadline d = std::chrono::seconds(30)) noexcept; }; +namespace detail +{ + extern LLFIO_DECL result<path_handle> containing_directory(optional<std::reference_wrapper<filesystem::path>> out_filename, const handle &h, const fs_handle &fsh, deadline d) noexcept; +} + // BEGIN make_free_functions.py /*! Relinks the current path of this open handle to the new path specified. If `atomic_replace` is true, the relink \b atomically and silently replaces any item at the new path specified. This operation diff --git a/include/llfio/v2.0/path_handle.hpp b/include/llfio/v2.0/path_handle.hpp index e2ee965c..87798c04 100644 --- a/include/llfio/v2.0/path_handle.hpp +++ b/include/llfio/v2.0/path_handle.hpp @@ -93,6 +93,16 @@ public: //! \overload LLFIO_MAKE_FREE_FUNCTION static LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result<path_handle> path(path_view_type _path) noexcept { return path(path_handle(), _path); } + + /*! Clone this handle (copy constructor is disabled to avoid accidental copying). + */ + result<path_handle> clone() const noexcept + { + auto *h = static_cast<const handle *>(this); + OUTCOME_TRY(ret, h->clone()); + auto nativeh = ret.release(); + return path_handle(nativeh); + } }; //! \brief Constructor for `path_handle` diff --git a/include/llfio/v2.0/status_code.hpp b/include/llfio/v2.0/status_code.hpp index 5c240316..79edcde4 100644 --- a/include/llfio/v2.0/status_code.hpp +++ b/include/llfio/v2.0/status_code.hpp @@ -451,25 +451,53 @@ OUTCOME_TEMPLATE(class ErrorCondEnum) OUTCOME_TREQUIRES(OUTCOME_TPRED(std::is_error_condition_enum<ErrorCondEnum>::value)) inline bool operator==(const error_info &a, const ErrorCondEnum &b) { - return make_error_code(a) == std::error_condition(b); + auto _a = make_error_code(a); + auto _b = std::error_condition(b); +#ifndef _WIN32 + // Looks like libstdc++ doesn't map system category to generic category, which is a bug + if(_a.category() == std::system_category() && _b.category() == std::generic_category() && _a.value() == static_cast<int>(b)) + return true; +#endif + return _a == _b; } OUTCOME_TEMPLATE(class ErrorCondEnum) OUTCOME_TREQUIRES(OUTCOME_TPRED(std::is_error_condition_enum<ErrorCondEnum>::value)) inline bool operator==(const ErrorCondEnum &a, const error_info &b) { - return std::error_condition(a) == make_error_code(b); + auto _a = std::error_condition(a); + auto _b = make_error_code(b); +#ifndef _WIN32 + // Looks like libstdc++ doesn't map system category to generic category, which is a bug + if(_a.category() == std::generic_category() && _b.category() == std::system_category() && _b.value() == static_cast<int>(a)) + return true; +#endif + return _a == _b; } OUTCOME_TEMPLATE(class ErrorCondEnum) OUTCOME_TREQUIRES(OUTCOME_TPRED(std::is_error_condition_enum<ErrorCondEnum>::value)) inline bool operator!=(const error_info &a, const ErrorCondEnum &b) { - return make_error_code(a) != std::error_condition(b); + auto _a = make_error_code(a); + auto _b = std::error_condition(b); +#ifndef _WIN32 + // Looks like libstdc++ doesn't map system category to generic category, which is a bug + if(_a.category() == std::system_category() && _b.category() == std::generic_category() && _a.value() == static_cast<int>(b)) + return false; +#endif + return _a != _b; } OUTCOME_TEMPLATE(class ErrorCondEnum) OUTCOME_TREQUIRES(OUTCOME_TPRED(std::is_error_condition_enum<ErrorCondEnum>::value)) inline bool operator!=(const ErrorCondEnum &a, const error_info &b) { - return std::error_condition(a) != make_error_code(b); + auto _a = std::error_condition(a); + auto _b = make_error_code(b); +#ifndef _WIN32 + // Looks like libstdc++ doesn't map system category to generic category, which is a bug + if(_a.category() == std::generic_category() && _b.category() == std::system_category() && _b.value() == static_cast<int>(a)) + return false; +#endif + return _a != _b; } #ifndef NDEBUG // Is trivial in all ways, except default constructibility diff --git a/include/llfio/v2.0/symlink_handle.hpp b/include/llfio/v2.0/symlink_handle.hpp index 179b820a..266f4d79 100644 --- a/include/llfio/v2.0/symlink_handle.hpp +++ b/include/llfio/v2.0/symlink_handle.hpp @@ -31,7 +31,7 @@ Distributed under the Boost Software License, Version 1.0. //! \file symlink_handle.hpp Provides a handle to a symbolic link. #ifndef LLFIO_SYMLINK_HANDLE_IS_FAKED -#if defined(_WIN32) || defined(__Linux__) +#if defined(_WIN32) //|| defined(__linux__) #define LLFIO_SYMLINK_HANDLE_IS_FAKED 0 #else #define LLFIO_SYMLINK_HANDLE_IS_FAKED 1 @@ -47,6 +47,11 @@ LLFIO_V2_NAMESPACE_EXPORT_BEGIN class symlink_handle; +namespace detail +{ + LLFIO_HEADERS_ONLY_FUNC_SPEC result<void> stat_from_symlink(struct stat &s, const handle &h) noexcept; +} + /*! \class symlink_handle \brief A handle to an inode which redirects to a different path. @@ -58,16 +63,16 @@ 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. +checking is enabled, this class will simply refuse to work with `errc::no_such_file_or_directory` +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 +directory junctions, and NTFS symbolic links. Reads of 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. +links was not historically permitted by users with ordinary permissions on Microsoft Windows, +however recent versions of Windows 10 do support symbolic links for ordinary users. All versions +of Windows support directory symbolic links (junctions), these work for all users in any configuration. */ class LLFIO_DECL symlink_handle : public handle, public fs_handle { @@ -78,6 +83,11 @@ class LLFIO_DECL symlink_handle : public handle, public fs_handle #endif LLFIO_HEADERS_ONLY_VIRTUAL_SPEC const handle &_get_handle() const noexcept final { return *this; } +#ifndef _WIN32 + friend LLFIO_HEADERS_ONLY_FUNC_SPEC result<void> detail::stat_from_symlink(struct stat &s, const handle &h) noexcept; + result<void> _create_symlink(path_view target, deadline d, bool atomic_replace) noexcept; +#endif + public: using path_type = handle::path_type; using extent_type = handle::extent_type; @@ -234,10 +244,10 @@ public: 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; + //! The i/o request type used by this handle. + template <class T, bool = true> struct io_request; //! Specialisation for reading symlinks - template <> struct io_request<buffers_type> + template <bool ____> struct io_request<buffers_type, ____> // workaround lack of nested specialisation support on older compilers { span<char> kernelbuffer{}; @@ -253,12 +263,12 @@ public: 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> + template <bool ____> struct io_request<const_buffers_type, ____> // workaround lack of nested specialisation support on older compilers { 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>()) + io_request(const_buffers_type _buffers, span<char> _kernelbuffer = span<char>()) : buffers(std::move(_buffers)) , kernelbuffer(_kernelbuffer) { @@ -327,7 +337,14 @@ public: } } } +#if !LLFIO_SYMLINK_HANDLE_IS_FAKED return handle::close(); +#else + _dirh = {}; + _leafname = path_type(); + _v = {}; + return success(); +#endif } /*! Clone this handle (copy constructor is disabled to avoid accidental copying), @@ -344,18 +361,48 @@ public: 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; + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result<path_type> current_path() const 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. + In this situation a failure comparing equal to `errc::function_not_supported` shall + be returned. + + \errors Any of the values POSIX open() or CreateFile() can return. + \mallocs None, unless `LLFIO_SYMLINK_HANDLE_IS_FAKED` is on, in which case one. */ 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; + /*! Create a symlink handle creating a randomly named symlink on a path. + The symlink is opened exclusively with `creation::only_if_not_exist` so it + will never collide with nor overwrite any existing symlink. + + \errors Any of the values POSIX open() or CreateFile() can return, + or failure to allocate memory. + */ + LLFIO_MAKE_FREE_FUNCTION + static inline result<symlink_handle> random_symlink(const path_handle &dirpath, mode _mode = mode::write, flag flags = flag::none) noexcept + { + try + { + for(;;) + { + auto randomname = utils::random_string(32); + randomname.append(".random"); + result<symlink_handle> ret = symlink(dirpath, randomname, _mode, creation::only_if_not_exist, flags); + if(ret || (!ret && ret.error() != errc::file_exists)) + { + return ret; + } + } + } + catch(...) + { + return error_from_exception(); + } + } /*! Read the contents of the symbolic link. @@ -363,13 +410,14 @@ public: 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. + \return Returns the buffers filled, with its path adjusted to the bytes filled. + \param req 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 allocated internally and stored into `req.buffers` 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. + \errors Any of the errors which `readlinkat()` or `DeviceIoControl()` might return, or failure + to allocate memory if the user did not supply a kernel buffer to use, or the user supplied buffer + was too small. \mallocs If the `kernelbuffer` parameter is set on entry, no memory allocations. If unset, then at least one memory allocation, possibly more is performed. */ @@ -379,12 +427,15 @@ public: /*! 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. + \param d An optional deadline by which the i/o must complete, else it is cancelled. Ignored + on Windows. + \errors Any of the errors which `symlinkat()` or `DeviceIoControl()` might return. + \mallocs On Windows, if the `kernelbuffer` parameter is set on entry, no memory allocations. + If unset, then at least one memory allocation, possibly more is performed. On POSIX, + at least one memory allocation. */ LLFIO_MAKE_FREE_FUNCTION - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result<const_buffers_type> write(io_request<const_buffers_type> req) noexcept; + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result<const_buffers_type> write(io_request<const_buffers_type> req, deadline d = deadline()) noexcept; }; //! \brief Constructor for `symlink_handle` |