/* A parent handle caching adapter (C) 2017 Niall Douglas (12 commits) File Created: Sept 2017 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_CACHED_PARENT_HANDLE_ADAPTER_HPP #define LLFIO_CACHED_PARENT_HANDLE_ADAPTER_HPP #include "../../directory_handle.hpp" #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4251) // dll interface #endif //! \file cached_parent_handle_adapter.hpp Adapts any `fs_handle` to cache its parent directory handle LLFIO_V2_NAMESPACE_BEGIN namespace algorithm { namespace detail { struct LLFIO_DECL cached_path_handle : public std::enable_shared_from_this { directory_handle h; filesystem::path _lastpath; explicit cached_path_handle(directory_handle &&_h) : h(std::move(_h)) { } LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result current_path(const filesystem::path &append) noexcept; }; using cached_path_handle_ptr = std::shared_ptr; // Passed the base and path of the adapted handle being created, returns a handle to the containing directory and the leafname LLFIO_HEADERS_ONLY_FUNC_SPEC std::pair get_cached_path_handle(const path_handle &base, path_view path); } // namespace detail /*! \brief Adapts any `construct()`-able implementation to cache its parent directory handle in a process wide cache. For some use cases where one is calling `parent_path_handle()` or code which calls that function very frequently e.g. calling `relink()` or `unlink()` a lot on many files with the same parent directory, having to constantly fetch the current path, open the parent directory and verify inodes becomes unhelpfully inefficient. This adapter keeps a process-wide hash table of directory handles shared between all instances of this adapter, thus making calling `parent_path_handle()` almost zero cost. This adapter is of especial use on platforms which do not reliably implement per-fd path tracking for regular files (Apple MacOS, FreeBSD) as `current_path()` is reimplemented to use the current path of the shared parent directory instead. One loses race freedom within the contained directory, but that is the case on POSIX anyway. This adapter is also of use on platforms which do not implement path tracking for open handles at all (e.g. Linux without `/proc` mounted) as the process-wide cache of directory handles retains the path of the directory handle at the time of creation. Third party changes to the part of the filesystem you are working upon will result in the inability to do race free unlinking etc, but if no third party changes are encountered it ought to work well. \todo I have been lazy and used public inheritance from that base i/o handle. I should use protected inheritance to prevent slicing, and expose all the public functions by hand. */ template LLFIO_REQUIRES(sizeof(construct) > 0) class LLFIO_DECL cached_parent_handle_adapter : public T { static_assert(sizeof(construct) > 0, "Type T must be registered with the construct framework so cached_parent_handle_adapter knows how to construct it"); // NOLINT public: //! The handle type being adapted using adapted_handle_type = T; using path_type = typename T::path_type; using path_view_type = typename T::path_view_type; protected: detail::cached_path_handle_ptr _sph; filesystem::path _leafname; public: cached_parent_handle_adapter() = default; cached_parent_handle_adapter(const cached_parent_handle_adapter &) = default; cached_parent_handle_adapter(cached_parent_handle_adapter &&) = default; // NOLINT cached_parent_handle_adapter &operator=(const cached_parent_handle_adapter &) = default; cached_parent_handle_adapter &operator=(cached_parent_handle_adapter &&o) noexcept { this->~cached_parent_handle_adapter(); new(this) cached_parent_handle_adapter(std::move(o)); return *this; } cached_parent_handle_adapter(adapted_handle_type &&o, const path_handle &base, path_view path) : adapted_handle_type(std::move(o)) { auto r = detail::get_cached_path_handle(base, path); _sph = std::move(r.first); _leafname = std::move(r.second); } LLFIO_HEADERS_ONLY_VIRTUAL_SPEC ~cached_parent_handle_adapter() override { if(this->_v) { (void) cached_parent_handle_adapter::close(); } } LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result current_path() const noexcept override { LLFIO_LOG_FUNCTION_CALL(this); return (_sph != nullptr) ? _sph->current_path(_leafname) : path_type(); } LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result close() noexcept override { LLFIO_LOG_FUNCTION_CALL(this); OUTCOME_TRYV(adapted_handle_type::close()); _sph.reset(); _leafname.clear(); return success(); } LLFIO_HEADERS_ONLY_VIRTUAL_SPEC native_handle_type release() noexcept override { LLFIO_LOG_FUNCTION_CALL(this); _sph.reset(); _leafname.clear(); return adapted_handle_type::release(); } LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result parent_path_handle(deadline /* unused */ = std::chrono::seconds(30)) const noexcept override { LLFIO_LOG_FUNCTION_CALL(this); OUTCOME_TRY(ret, _sph->h.clone_to_path_handle()); return {std::move(ret)}; } LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result relink(const path_handle &base, path_view_type newpath, bool atomic_replace = true, deadline d = std::chrono::seconds(30)) noexcept override { LLFIO_LOG_FUNCTION_CALL(this); OUTCOME_TRYV(adapted_handle_type::relink(base, newpath, atomic_replace, d)); _sph.reset(); _leafname.clear(); try { auto r = detail::get_cached_path_handle(base, newpath); _sph = std::move(r.first); _leafname = std::move(r.second); return success(); } catch(...) { return error_from_exception(); } } LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result unlink(deadline d = std::chrono::seconds(30)) noexcept override { LLFIO_LOG_FUNCTION_CALL(this); OUTCOME_TRYV(adapted_handle_type::unlink(d)); _sph.reset(); _leafname.clear(); return success(); } }; /*! \brief Constructs a `T` adapted into a parent handle caching implementation. This function works via the `construct()` free function framework for which your `handle` implementation must have registered its construction details. */ template inline result> cache_parent(Args &&... args) noexcept { construct constructor{std::forward(args)...}; OUTCOME_TRY(h, constructor()); try { return cached_parent_handle_adapter(std::move(h), constructor.base, constructor._path); } catch(...) { return error_from_exception(); } } } // namespace algorithm //! \brief Constructor for `algorithm::::cached_parent_handle_adapter` template struct construct> { construct args; result> operator()() const noexcept { OUTCOME_TRY(h, args()); try { return algorithm::cached_parent_handle_adapter(std::move(h), args.base, args._path); } catch(...) { return error_from_exception(); } } }; LLFIO_V2_NAMESPACE_END #if LLFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS) #define LLFIO_INCLUDED_BY_HEADER 1 #include "../../detail/impl/cached_parent_handle_adapter.ipp" #undef LLFIO_INCLUDED_BY_HEADER #endif #ifdef _MSC_VER #pragma warning(pop) #endif #endif