/* 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)
*/
#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 stat_from_symlink(struct stat &s, const handle &_h) noexcept
{
#if !LLFIO_SYMLINK_HANDLE_IS_FAKED
(void) s;
(void) _h;
return posix_error(EBADF);
#else
const auto &h = static_cast(_h);
if(-1 == ::fstatat(h._dirh.native_handle().fd, h._leafname.c_str(), &s, AT_SYMLINK_NOFOLLOW))
{
return posix_error();
}
return success();
#endif
}
} // namespace detail
LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result symlink_handle::_create_symlink(const path_handle &dirh, const handle::path_type &filename, path_view target,
deadline d, bool atomic_replace, bool exists_is_ok) 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, path_view::zero_terminated);
try
{
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");
// std::cerr << "symlinkat " << zpath.buffer << " " << dirh.native_handle().fd << " " << randomname << std::endl;
if(-1 == ::symlinkat(zpath.buffer, dirh.is_valid() ? dirh.native_handle().fd : AT_FDCWD, 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();
}
// std::cerr << "renameat " << dirh.native_handle().fd << " " << randomname << " " << filename << std::endl;
if(-1 == ::renameat(dirh.is_valid() ? dirh.native_handle().fd : AT_FDCWD, randomname.c_str(), dirh.is_valid() ? dirh.native_handle().fd : AT_FDCWD,
filename.c_str()))
{
return posix_error();
}
return success();
}
}
else
{
// std::cerr << "symlinkat " << zpath.buffer << " " << dirh.native_handle().fd << " " << filename << std::endl;
if(-1 == ::symlinkat(zpath.buffer, dirh.is_valid() ? dirh.native_handle().fd : AT_FDCWD, filename.c_str()))
{
if(exists_is_ok && EEXIST == errno)
{
return success();
}
return posix_error();
}
return success();
}
}
catch(...)
{
return error_from_exception();
}
}
result symlink_handle::reopen(mode mode_, deadline d) const noexcept
{
LLFIO_LOG_FUNCTION_CALL(this);
#if LLFIO_SYMLINK_HANDLE_IS_FAKED
result ret(symlink_handle(native_handle_type(), _devid, _inode, _flags));
ret.value()._v.behaviour = _v.behaviour;
OUTCOME_TRY(auto &&dirh, _dirh.clone());
ret.value()._dirh = path_handle(std::move(dirh));
try
{
ret.value()._leafname = _leafname;
}
catch(...)
{
return error_from_exception();
}
return ret;
#endif
// fast path
if(mode_ == mode::unchanged)
{
result 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, 0);
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(auto &¤tpath, 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 std::move(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::current_path() const noexcept
{
LLFIO_LOG_FUNCTION_CALL(this);
try
{
// Deleted?
if(!_dirh.is_valid() && _leafname.empty())
{
return _leafname;
}
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(auto &&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 {std::move(dirpath)};
}
}
catch(...)
{
return error_from_exception();
}
}
result symlink_handle::relink(const path_handle &base, path_view_type path, bool atomic_replace, deadline d) noexcept
{
LLFIO_LOG_FUNCTION_CALL(this);
OUTCOME_TRY(fs_handle::relink(base, path, atomic_replace, d));
try
{
// Take a path handle to the directory containing the symlink
auto path_parent = path.parent_path();
_leafname = path.filename().path();
if(base.is_valid() && path_parent.empty())
{
OUTCOME_TRY(auto &&dh, base.clone());
_dirh = path_handle(std::move(dh));
}
else if(!path_parent.empty())
{
OUTCOME_TRY(auto &&dh, path_handle::path(base, path_parent));
_dirh = std::move(dh);
}
}
catch(...)
{
return error_from_exception();
}
return success();
}
result symlink_handle::unlink(deadline d) noexcept
{
LLFIO_LOG_FUNCTION_CALL(this);
OUTCOME_TRY(fs_handle::unlink(d));
_dirh = {};
_leafname = path_type();
return success();
}
#endif
LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result 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 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_existing)
{
return errc::function_not_supported;
}
OUTCOME_TRY(auto &&attribs, attribs_from_handle_mode_caching_and_flags(nativeh, _mode, _creation, caching::all, flags));
attribs &= ~O_NONBLOCK;
nativeh.behaviour &= ~native_handle_type::disposition::nonblocking;
nativeh.behaviour &= ~native_handle_type::disposition::seekable; // not seekable
#if !LLFIO_SYMLINK_HANDLE_IS_FAKED
path_handle _dirh_, *dirh = &_dirh_;
path_type leafname;
#else
(void) attribs;
path_handle *dirh = &ret.value()._dirh;
path_type &leafname = ret.value()._leafname;
#endif
int dirhfd = AT_FDCWD;
try
{
// Take a path handle to the directory containing the symlink
auto path_parent = path.parent_path();
leafname = path.filename().path();
if(base.is_valid() && path_parent.empty())
{
#if !LLFIO_SYMLINK_HANDLE_IS_FAKED
dirhfd = base.native_handle().fd;
dirh = const_cast(&base);
#else
OUTCOME_TRY(auto dh, base.clone());
*dirh = path_handle(std::move(dh));
dirhfd = dirh->native_handle().fd;
#endif
}
else
#if !LLFIO_SYMLINK_HANDLE_IS_FAKED // always take a dirh if faking the handle for race safety
if(!path_parent.empty())
#endif
{
// If faking the symlink, write this directly into the member variable cache
OUTCOME_TRY(*dirh, path_handle::path(base, path_parent.empty() ? "." : path_parent));
dirhfd = dirh->native_handle().fd;
}
}
catch(...)
{
return error_from_exception();
}
switch(_creation)
{
case creation::open_existing:
{
// Complain if it doesn't exist
struct stat s;
if(-1 == ::fstatat(dirhfd, leafname.c_str(), &s, AT_SYMLINK_NOFOLLOW))
{
return posix_error();
}
break;
}
case creation::only_if_not_exist:
case creation::if_needed:
case creation::truncate_existing:
case creation::always_new:
{
// Create an empty symlink, ignoring any file exists errors, unless only_if_not_exist
auto r = ret.value()._create_symlink(*dirh, leafname,
#if defined(__linux__) || defined(__APPLE__)
".", // Linux and Mac OS is not POSIX conforming here, and refuses to create empty symlinks
#else
"",
#endif
std::chrono::seconds(10), creation::always_new == _creation, creation::if_needed == _creation);
if(!r)
{
return std::move(r).error();
}
break;
}
}
#if !LLFIO_SYMLINK_HANDLE_IS_FAKED
// Linux can open symbolic links directly like this
attribs |= O_PATH | O_NOFOLLOW;
nativeh.fd = ::openat(dirhfd, leafname.c_str(), attribs, 0x1b0 /*660*/);
if(-1 == nativeh.fd)
{
return posix_error();
}
#endif
return ret;
}
result symlink_handle::read(symlink_handle::io_request 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(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(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, path_view::zero_terminated);
tofill._type = symlink_type::symbolic;
return {std::move(tofill)};
}
}
result symlink_handle::write(symlink_handle::io_request req, deadline d) noexcept
{
LLFIO_LOG_FUNCTION_CALL(this);
#if !LLFIO_SYMLINK_HANDLE_IS_FAKED
if(_devid == 0 && _inode == 0)
{
OUTCOME_TRY(_fetch_inode());
}
path_type filename;
OUTCOME_TRY(auto &&dirh, detail::containing_directory(std::ref(filename), *this, *this, d));
#else
const path_handle &dirh = _dirh;
const path_type &filename = _leafname;
#endif
OUTCOME_TRY(_create_symlink(dirh, filename, req.buffers.path(), d, true, false));
#if !LLFIO_SYMLINK_HANDLE_IS_FAKED
{
// Current fd now points at the symlink we just atomically replaced, so need to reopen
// it onto the new symlink
auto newthis = symlink(dirh, filename, is_writable() ? mode::write : mode::read, creation::open_existing, _flags);
// Prevent unlink on first close if set
_flags &= ~flag::unlink_on_first_close;
// Close myself
OUTCOME_TRY(close());
// If reopen failed, report that now
if(!newthis)
{
return std::move(newthis).error();
}
// Swap myself with the new this
swap(newthis.value());
}
#endif
return success(std::move(req.buffers));
}
LLFIO_V2_NAMESPACE_END