/* 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;
namespace detail
{
LLFIO_HEADERS_ONLY_FUNC_SPEC result 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.
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 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, 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 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
{
#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; }
#ifndef _WIN32
friend result detail::stat_from_symlink(struct stat &s, const handle &h) noexcept;
result _create_symlink(const path_handle &dirh, const handle::path_type &filename, path_view target, deadline d, bool atomic_replace) noexcept;
#endif
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.
template struct io_request;
//! Specialisation for reading symlinks
template struct io_request // workaround lack of nested specialisation support on older compilers
{
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 // workaround lack of nested specialisation support on older compilers
{
const_buffers_type buffers;
span kernelbuffer;
//! Construct a request to write a link with optionally specified kernel buffer
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 &&o) noexcept
{
this->~symlink_handle();
new(this) symlink_handle(std::move(o));
return *this;
}
//! No copy assignment
symlink_handle &operator=(const symlink_handle &) = delete;
//! Swap with another instance
LLFIO_MAKE_FREE_FUNCTION
void swap(symlink_handle &o) noexcept
{
symlink_handle temp(std::move(*this));
*this = std::move(o);
o = std::move(temp);
}
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();
}
}
}
#if !LLFIO_SYMLINK_HANDLE_IS_FAKED
#ifndef NDEBUG
if(_v)
{
// Tell handle::close() that we have correctly executed
_v.behaviour |= native_handle_type::disposition::_child_close_executed;
}
#endif
return handle::close();
#else
_dirh = {};
_leafname = path_type();
_v = {};
return success();
#endif
}
/*! 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 current_path() const noexcept override;
LLFIO_MAKE_FREE_FUNCTION
LLFIO_HEADERS_ONLY_VIRTUAL_SPEC
result relink(const path_handle &base, path_view_type path, bool atomic_replace = true, deadline d = std::chrono::seconds(30)) noexcept override;
LLFIO_MAKE_FREE_FUNCTION
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.
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(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 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 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.
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 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 `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 `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.
*/
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.
\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 write(io_request req, deadline d = deadline()) 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