From 964d174c1c44d4810789b198bbf07271cebbe8c8 Mon Sep 17 00:00:00 2001 From: "Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com)" Date: Tue, 24 Jul 2018 20:29:21 +0100 Subject: i/o buffers now work as if they were span. Implemented many of the missing functions in path_view. Implemented symlink_handle for Windows. --- include/llfio/v2.0/symlink_handle.hpp | 417 ++++++++++++++++++++++++++++++++++ 1 file changed, 417 insertions(+) create mode 100644 include/llfio/v2.0/symlink_handle.hpp (limited to 'include/llfio/v2.0/symlink_handle.hpp') 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 (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, //!~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 _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 struct io_request; + //! Specialisation for reading symlinks + template <> struct io_request + { + span kernelbuffer{}; + + constexpr io_request() {} // NOLINT + //! Construct a request to read a link with optionally specified kernel buffer + constexpr io_request(span _kernelbuffer) + : kernelbuffer(_kernelbuffer) + { + } + //! Convenience constructor constructing from anything a `span` can construct from + LLFIO_TEMPLATE(class... Args) + LLFIO_TREQUIRES(LLFIO_TPRED(std::is_constructible, Args...>::value)) + constexpr io_request(Args &&... args) noexcept : io_request(span(static_cast(args)...)) {} + }; + //! Specialisation for writing symlinks + template <> struct io_request + { + const_buffers_type buffers; + span kernelbuffer; + //! Construct a request to write a link with optionally specified kernel buffer + constexpr io_request(const_buffers_type _buffers, span _kernelbuffer = span()) + : 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::value)) + constexpr io_request(Args &&... args) noexcept : buffers(path_view(static_cast(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::value)) + constexpr io_request(symlink_type type, Args &&... args) noexcept : buffers(path_view(static_cast(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 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 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 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 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(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 read(io_request 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 write(io_request req) noexcept; +}; + +//! \brief Constructor for `symlink_handle` +template <> struct construct +{ + 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 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 -- cgit v1.2.3