Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/windirstat/llfio.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'include/llfio')
-rw-r--r--include/llfio/revision.hpp6
-rw-r--r--include/llfio/v2.0/deadline.h17
-rw-r--r--include/llfio/v2.0/detail/impl/posix/fs_handle.ipp144
-rw-r--r--include/llfio/v2.0/detail/impl/posix/stat.ipp12
-rw-r--r--include/llfio/v2.0/detail/impl/posix/symlink_handle.ipp396
-rw-r--r--include/llfio/v2.0/detail/impl/windows/fs_handle.ipp2
-rw-r--r--include/llfio/v2.0/detail/impl/windows/import.hpp15
-rw-r--r--include/llfio/v2.0/detail/impl/windows/io_handle.ipp4
-rw-r--r--include/llfio/v2.0/detail/impl/windows/symlink_handle.ipp2
-rw-r--r--include/llfio/v2.0/fs_handle.hpp5
-rw-r--r--include/llfio/v2.0/path_handle.hpp10
-rw-r--r--include/llfio/v2.0/status_code.hpp36
-rw-r--r--include/llfio/v2.0/symlink_handle.hpp103
13 files changed, 623 insertions, 129 deletions
diff --git a/include/llfio/revision.hpp b/include/llfio/revision.hpp
index dfdb3db0..244d4668 100644
--- a/include/llfio/revision.hpp
+++ b/include/llfio/revision.hpp
@@ -1,4 +1,4 @@
// Note the second line of this file must ALWAYS be the git SHA, third line ALWAYS the git SHA update time
-#define LLFIO_PREVIOUS_COMMIT_REF 084b3eb8aaae639e9a63e97245386fd78b8d8413
-#define LLFIO_PREVIOUS_COMMIT_DATE "2018-07-13 19:20:14 +00:00"
-#define LLFIO_PREVIOUS_COMMIT_UNIQUE 084b3eb8
+#define LLFIO_PREVIOUS_COMMIT_REF 964d174c1c44d4810789b198bbf07271cebbe8c8
+#define LLFIO_PREVIOUS_COMMIT_DATE "2018-07-24 19:29:25 +00:00"
+#define LLFIO_PREVIOUS_COMMIT_UNIQUE 964d174c
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`