Reworked directory iterator construction with parameters.

Instead of passing a base directory fd, pass the directory fd to iterate over.
This simplifies code a little and makes it closer to Windows version.

Also, use unique_fd from Boost.Scope instead of a custom fd wrapper. Also
added handling of EINTR returned from open/openat in more places.
This commit is contained in:
Andrey Semashev 2024-02-18 20:52:39 +03:00
parent 9f6bf1a433
commit e9621c0585
5 changed files with 320 additions and 196 deletions

View File

@ -239,6 +239,7 @@ target_link_libraries(boost_filesystem
PRIVATE
Boost::predef
Boost::scope
)
if(NOT BOOST_FILESYSTEM_HAS_CXX20_ATOMIC_REF)

View File

@ -2,7 +2,7 @@
// Copyright 2002-2009, 2014 Beman Dawes
// Copyright 2001 Dietmar Kuehl
// Copyright 2019, 2022 Andrey Semashev
// Copyright 2019, 2022-2024 Andrey Semashev
// Distributed under the Boost Software License, Version 1.0.
// See http://www.boost.org/LICENSE_1_0.txt
@ -40,6 +40,8 @@
#include <unistd.h>
#include <fcntl.h>
#include <boost/scope/unique_fd.hpp>
#if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && (_POSIX_THREAD_SAFE_FUNCTIONS >= 0) && defined(_SC_THREAD_SAFE_FUNCTIONS) && \
!defined(__CYGWIN__) && \
!(defined(linux) || defined(__linux) || defined(__linux__)) && \
@ -123,6 +125,104 @@ BOOST_FILESYSTEM_DECL void directory_entry::refresh_impl(system::error_code* ec)
namespace detail {
#if defined(BOOST_POSIX_API)
//! Opens a directory file and returns a file descriptor. Returns a negative value in case of error.
boost::scope::unique_fd open_directory(path const& p, directory_options opts, system::error_code& ec)
{
ec.clear();
int flags = O_DIRECTORY | O_RDONLY | O_NONBLOCK | O_CLOEXEC;
#if defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW)
if ((opts & directory_options::_detail_no_follow) != directory_options::none)
flags |= O_NOFOLLOW;
#endif
int res;
while (true)
{
res = ::open(p.c_str(), flags);
if (BOOST_UNLIKELY(res < 0))
{
const int err = errno;
if (err == EINTR)
continue;
ec = system::error_code(err, system::system_category());
return boost::scope::unique_fd();
}
break;
}
#if defined(BOOST_FILESYSTEM_NO_O_CLOEXEC) && defined(FD_CLOEXEC)
boost::scope::unique_fd fd(res);
res = ::fcntl(fd.get(), F_SETFD, FD_CLOEXEC);
if (BOOST_UNLIKELY(res < 0))
{
const int err = errno;
ec = system::error_code(err, system::system_category());
return boost::scope::unique_fd();
}
return fd;
#else
return boost::scope::unique_fd(res);
#endif
}
#if defined(BOOST_FILESYSTEM_HAS_POSIX_AT_APIS)
//! Opens a directory file and returns a file descriptor. Returns a negative value in case of error.
boost::scope::unique_fd openat_directory(int basedir_fd, path const& p, directory_options opts, system::error_code& ec)
{
ec.clear();
int flags = O_DIRECTORY | O_RDONLY | O_NONBLOCK | O_CLOEXEC;
#if defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW)
if ((opts & directory_options::_detail_no_follow) != directory_options::none)
flags |= O_NOFOLLOW;
#endif
int res;
while (true)
{
res = ::openat(basedir_fd, p.c_str(), flags);
if (BOOST_UNLIKELY(res < 0))
{
const int err = errno;
if (err == EINTR)
continue;
ec = system::error_code(err, system::system_category());
return boost::scope::unique_fd();
}
break;
}
#if defined(BOOST_FILESYSTEM_NO_O_CLOEXEC) && defined(FD_CLOEXEC)
boost::scope::unique_fd fd(res);
res = ::fcntl(fd.get(), F_SETFD, FD_CLOEXEC);
if (BOOST_UNLIKELY(res < 0))
{
const int err = errno;
ec = system::error_code(err, system::system_category());
return boost::scope::unique_fd();
}
return fd;
#else
return boost::scope::unique_fd(res);
#endif
}
#endif // defined(BOOST_FILESYSTEM_HAS_POSIX_AT_APIS)
#endif // defined(BOOST_POSIX_API)
BOOST_CONSTEXPR_OR_CONST std::size_t dir_itr_imp_extra_data_alignment = 16u;
BOOST_FILESYSTEM_DECL void* dir_itr_imp::operator new(std::size_t class_size, std::size_t extra_size) noexcept
@ -168,7 +268,7 @@ inline system::error_code dir_itr_close(dir_itr_imp& imp) noexcept
if (BOOST_UNLIKELY(::closedir(h) != 0))
{
err = errno;
return error_code(err, system_category());
return system::error_code(err, system::system_category());
}
}
@ -308,12 +408,12 @@ inline int invoke_readdir(dir_itr_imp& imp, struct dirent** result)
#endif // !defined(BOOST_FILESYSTEM_USE_READDIR_R)
error_code dir_itr_increment(dir_itr_imp& imp, fs::path& filename, fs::file_status& sf, fs::file_status& symlink_sf)
system::error_code dir_itr_increment(dir_itr_imp& imp, fs::path& filename, fs::file_status& sf, fs::file_status& symlink_sf)
{
dirent* result = nullptr;
int err = invoke_readdir(imp, &result);
if (BOOST_UNLIKELY(err != 0))
return error_code(err, system_category());
return system::error_code(err, system::system_category());
if (result == nullptr)
return dir_itr_close(imp);
@ -360,10 +460,10 @@ error_code dir_itr_increment(dir_itr_imp& imp, fs::path& filename, fs::file_stat
#else
sf = symlink_sf = fs::file_status(fs::status_error);
#endif
return error_code();
return system::error_code();
}
error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::path const& dir, directory_options opts, directory_iterator_params* params, fs::path& first_filename, fs::file_status&, fs::file_status&)
system::error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::path const& dir, directory_options opts, directory_iterator_params* params, fs::path& first_filename, fs::file_status&, fs::file_status&)
{
std::size_t extra_size = 0u;
#if defined(BOOST_FILESYSTEM_USE_READDIR_R)
@ -392,57 +492,42 @@ error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::
return make_error_code(system::errc::not_enough_memory);
#if defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW)
int flags = O_DIRECTORY | O_RDONLY | O_NONBLOCK | O_CLOEXEC;
if ((opts & directory_options::_detail_no_follow) != directory_options::none)
flags |= O_NOFOLLOW;
#if defined(BOOST_FILESYSTEM_HAS_POSIX_AT_APIS)
int fd = ::openat(params ? params->basedir_fd : AT_FDCWD, dir.c_str(), flags);
#else
int fd = ::open(dir.c_str(), flags);
#endif
if (BOOST_UNLIKELY(fd < 0))
boost::scope::unique_fd fd;
if (params && params->dir_fd)
{
const int err = errno;
return error_code(err, system_category());
fd = std::move(params->dir_fd);
}
else
{
system::error_code ec;
fd = open_directory(dir, opts, ec);
if (BOOST_UNLIKELY(!!ec))
return ec;
}
#if defined(BOOST_FILESYSTEM_NO_O_CLOEXEC) && defined(FD_CLOEXEC)
int res = ::fcntl(fd, F_SETFD, FD_CLOEXEC);
if (BOOST_UNLIKELY(res < 0))
{
const int err = errno;
close_fd(fd);
return error_code(err, system_category());
}
#endif
pimpl->handle = ::fdopendir(fd);
pimpl->handle = ::fdopendir(fd.get());
if (BOOST_UNLIKELY(!pimpl->handle))
{
const int err = errno;
close_fd(fd);
return error_code(err, system_category());
return system::error_code(err, system::system_category());
}
// At this point fd will be closed by closedir
fd.release();
#else // defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW)
pimpl->handle = ::opendir(dir.c_str());
if (BOOST_UNLIKELY(!pimpl->handle))
{
const int err = errno;
return error_code(err, system_category());
return system::error_code(err, system::system_category());
}
#endif // defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW)
// Force initial readdir call by the caller. This will initialize the actual first filename and statuses.
first_filename.assign(".");
#if defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW)
if (params)
params->iterator_fd = fd;
#endif
imp.swap(pimpl);
return error_code();
return system::error_code();
}
BOOST_CONSTEXPR_OR_CONST err_t not_found_error_code = ENOENT;
@ -604,7 +689,7 @@ inline system::error_code dir_itr_close(dir_itr_imp& imp) noexcept
return error_code();
}
error_code dir_itr_increment(dir_itr_imp& imp, fs::path& filename, fs::file_status& sf, fs::file_status& symlink_sf)
system::error_code dir_itr_increment(dir_itr_imp& imp, fs::path& filename, fs::file_status& sf, fs::file_status& symlink_sf)
{
void* extra_data = get_dir_itr_imp_extra_data(&imp);
const void* current_data = static_cast< const unsigned char* >(extra_data) + imp.current_offset;
@ -623,7 +708,7 @@ error_code dir_itr_increment(dir_itr_imp& imp, fs::path& filename, fs::file_stat
if (error == ERROR_NO_MORE_FILES)
goto done;
return error_code(error, system_category());
return system::error_code(error, system::system_category());
}
imp.current_offset = 0u;
@ -653,7 +738,7 @@ error_code dir_itr_increment(dir_itr_imp& imp, fs::path& filename, fs::file_stat
if (error == ERROR_NO_MORE_FILES)
goto done;
return error_code(error, system_category());
return system::error_code(error, system::system_category());
}
imp.current_offset = 0u;
@ -683,7 +768,7 @@ error_code dir_itr_increment(dir_itr_imp& imp, fs::path& filename, fs::file_stat
if (error == ERROR_NO_MORE_FILES)
goto done;
return error_code(error, system_category());
return system::error_code(error, system::system_category());
}
imp.current_offset = 0u;
@ -727,7 +812,7 @@ error_code dir_itr_increment(dir_itr_imp& imp, fs::path& filename, fs::file_stat
if (status == STATUS_NO_MORE_FILES)
goto done;
return error_code(translate_ntstatus(status), system_category());
return system::error_code(translate_ntstatus(status), system::system_category());
}
imp.current_offset = 0u;
@ -746,7 +831,7 @@ error_code dir_itr_increment(dir_itr_imp& imp, fs::path& filename, fs::file_stat
}
done:
return error_code();
return system::error_code();
}
//! Returns \c true if the error code indicates that the OS or the filesystem does not support a particular directory info class
@ -771,7 +856,7 @@ inline bool is_dir_info_class_not_supported(DWORD error)
error == ERROR_INTERNAL_ERROR;
}
error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::path const& dir, directory_options opts, directory_iterator_params* params, fs::path& first_filename, fs::file_status& sf, fs::file_status& symlink_sf)
system::error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::path const& dir, directory_options opts, directory_iterator_params* params, fs::path& first_filename, fs::file_status& sf, fs::file_status& symlink_sf)
{
boost::intrusive_ptr< detail::dir_itr_imp > pimpl(new (dir_itr_extra_size) detail::dir_itr_imp());
if (BOOST_UNLIKELY(!pimpl))
@ -799,7 +884,7 @@ error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::
{
return_last_error:
DWORD error = ::GetLastError();
return error_code(error, system_category());
return system::error_code(error, system::system_category());
}
if (BOOST_LIKELY(get_file_information_by_handle_ex != nullptr))
@ -813,7 +898,7 @@ error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::
if (error == ERROR_INVALID_PARAMETER || error == ERROR_NOT_SUPPORTED)
goto use_get_file_information_by_handle;
return error_code(error, system_category());
return system::error_code(error, system::system_category());
}
if (BOOST_UNLIKELY((info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0u))
@ -869,7 +954,7 @@ error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::
if (error == ERROR_NO_MORE_FILES || error == ERROR_FILE_NOT_FOUND)
goto done;
return error_code(error, system_category());
return system::error_code(error, system::system_category());
}
pimpl->extra_data_format = file_id_extd_dir_info_format;
@ -899,7 +984,7 @@ error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::
if (error == ERROR_NO_MORE_FILES || error == ERROR_FILE_NOT_FOUND)
goto done;
return error_code(error, system_category());
return system::error_code(error, system::system_category());
}
pimpl->extra_data_format = file_full_dir_info_format;
@ -929,7 +1014,7 @@ error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::
if (error == ERROR_NO_MORE_FILES || error == ERROR_FILE_NOT_FOUND)
goto done;
return error_code(error, system_category());
return system::error_code(error, system::system_category());
}
pimpl->extra_data_format = file_id_both_dir_info_format;
@ -986,14 +1071,13 @@ error_code dir_itr_create(boost::intrusive_ptr< detail::dir_itr_imp >& imp, fs::
break;
}
pimpl->handle = iterator_handle;
h.handle = INVALID_HANDLE_VALUE;
pimpl->close_handle = close_handle;
done:
imp.swap(pimpl);
return error_code();
return system::error_code();
}
BOOST_CONSTEXPR_OR_CONST err_t not_found_error_code = ERROR_PATH_NOT_FOUND;
@ -1026,6 +1110,9 @@ dir_itr_imp::~dir_itr_imp() noexcept
BOOST_FILESYSTEM_DECL
void directory_iterator_construct(directory_iterator& it, path const& p, directory_options opts, directory_iterator_params* params, system::error_code* ec)
{
// At most one of the two options may be specified, and follow_directory_symlink is ignored for directory_iterator.
BOOST_ASSERT((opts & (directory_options::follow_directory_symlink | directory_options::_detail_no_follow)) != (directory_options::follow_directory_symlink | directory_options::_detail_no_follow));
if (BOOST_UNLIKELY(p.empty()))
{
emit_error(not_found_error_code, p, ec, "boost::filesystem::directory_iterator::construct");
@ -1037,21 +1124,6 @@ void directory_iterator_construct(directory_iterator& it, path const& p, directo
try
{
#if defined(BOOST_POSIX_API) && defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW) && defined(BOOST_FILESYSTEM_HAS_POSIX_AT_APIS)
path dir_path;
if (params)
{
dir_path = params->basedir;
path_algorithms::append_v4(dir_path, p);
}
else
{
dir_path = p;
}
#else
path const& dir_path = p;
#endif
boost::intrusive_ptr< detail::dir_itr_imp > imp;
path filename;
file_status file_stat, symlink_file_stat;
@ -1081,7 +1153,7 @@ void directory_iterator_construct(directory_iterator& it, path const& p, directo
&& (filename_str[1] == static_cast< path::string_type::value_type >('\0') ||
(filename_str[1] == path::dot && filename_str[2] == static_cast< path::string_type::value_type >('\0')))))
{
path full_path(dir_path);
path full_path(p);
path_algorithms::append_v4(full_path, filename);
imp->dir_entry.assign_with_status
(
@ -1172,6 +1244,9 @@ void directory_iterator_increment(directory_iterator& it, system::error_code* ec
BOOST_FILESYSTEM_DECL
void recursive_directory_iterator_construct(recursive_directory_iterator& it, path const& dir_path, directory_options opts, system::error_code* ec)
{
// At most one of the two options may be specified
BOOST_ASSERT((opts & (directory_options::follow_directory_symlink | directory_options::_detail_no_follow)) != (directory_options::follow_directory_symlink | directory_options::_detail_no_follow));
if (ec)
ec->clear();
@ -1400,6 +1475,11 @@ void recursive_directory_iterator_increment(recursive_directory_iterator& it, sy
{
directory_iterator const& dir_it = imp->m_stack.back();
// Don't query the file type from the filesystem yet, if not known. We will use dir_it for that below.
file_type ft = dir_it->m_status.type();
if (ft != status_error && ft != directory_file)
return result;
#if defined(BOOST_POSIX_API) && defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW) && defined(BOOST_FILESYSTEM_HAS_POSIX_AT_APIS)
if (parentdir_fd < 0)
{
@ -1410,12 +1490,28 @@ void recursive_directory_iterator_increment(recursive_directory_iterator& it, sy
dir_it_filename = detail::path_algorithms::filename_v4(dir_it->path());
}
file_type ft = status_error;
if (filesystem::type_present(dir_it->m_status))
ft = dir_it->m_status.type();
else
ft = detail::status_impl(dir_it_filename, &ec, parentdir_fd).type();
#elif defined(BOOST_WINDOWS_API)
// Try to open the file as a directory right away. This effectively tests whether the file is a directory, and, if it is, opens the directory in one system call.
detail::directory_iterator_params params{ detail::openat_directory(parentdir_fd, dir_it_filename, imp->m_options, ec) };
if (!!ec)
{
if
(
// Skip non-directory files
ec == system::error_code(ENOTDIR, system::system_category()) ||
(
// Skip dangling symlink, if requested by options
ec == system::error_code(ENOENT, system::system_category()) && symlink_ft == symlink_file &&
(imp->m_options & (directory_options::follow_directory_symlink | directory_options::skip_dangling_symlinks)) == (directory_options::follow_directory_symlink | directory_options::skip_dangling_symlinks)
)
)
{
ec.clear();
}
return result;
}
#else // defined(BOOST_POSIX_API) && defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW) && defined(BOOST_FILESYSTEM_HAS_POSIX_AT_APIS)
#if defined(BOOST_WINDOWS_API)
if (direntry_handle.handle != INVALID_HANDLE_VALUE && symlink_ft == symlink_file)
{
// Close the symlink to reopen the target file below
@ -1423,7 +1519,6 @@ void recursive_directory_iterator_increment(recursive_directory_iterator& it, sy
direntry_handle.handle = INVALID_HANDLE_VALUE;
}
file_type ft = symlink_ft;
if (direntry_handle.handle == INVALID_HANDLE_VALUE)
{
boost::winapi::NTSTATUS_ status = nt_create_file_handle_at
@ -1440,7 +1535,7 @@ void recursive_directory_iterator_increment(recursive_directory_iterator& it, sy
if (NT_SUCCESS(status))
{
ft = detail::status_by_handle(direntry_handle.handle, dir_it->path(), &ec).type();
goto get_file_type_by_handle;
}
else if (status == STATUS_NOT_IMPLEMENTED)
{
@ -1451,9 +1546,16 @@ void recursive_directory_iterator_increment(recursive_directory_iterator& it, sy
ec.assign(translate_ntstatus(status), system::system_category());
}
}
#else
file_type ft = dir_it->file_type(ec);
#endif
else
{
get_file_type_by_handle:
ft = detail::status_by_handle(direntry_handle.handle, dir_it->path(), &ec).type();
}
#else // defined(BOOST_WINDOWS_API)
if (ft == status_error)
ft = dir_it->file_type(ec);
#endif // defined(BOOST_WINDOWS_API)
if (BOOST_UNLIKELY(!!ec))
{
if (ec == make_error_condition(system::errc::no_such_file_or_directory) && symlink_ft == symlink_file &&
@ -1468,6 +1570,7 @@ void recursive_directory_iterator_increment(recursive_directory_iterator& it, sy
if (ft != directory_file)
return result;
#endif // defined(BOOST_POSIX_API) && defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW) && defined(BOOST_FILESYSTEM_HAS_POSIX_AT_APIS)
if (BOOST_UNLIKELY((imp->m_stack.size() - 1u) >= static_cast< std::size_t >((std::numeric_limits< int >::max)())))
{
@ -1480,25 +1583,22 @@ void recursive_directory_iterator_increment(recursive_directory_iterator& it, sy
}
#if defined(BOOST_POSIX_API) && defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW) && defined(BOOST_FILESYSTEM_HAS_POSIX_AT_APIS)
detail::directory_iterator_params params;
params.basedir = dir_it->path().parent_path();
params.basedir_fd = parentdir_fd;
params.iterator_fd = -1;
directory_iterator next;
detail::directory_iterator_construct(next, dir_it_filename, imp->m_options, &params, &ec);
detail::directory_iterator_construct(next, dir_it->path(), imp->m_options, &params, &ec);
#elif defined(BOOST_WINDOWS_API)
detail::directory_iterator_params params;
params.dir_handle = direntry_handle.handle;
params.close_handle = true;
directory_iterator next;
detail::directory_iterator_construct(next, dir_it->path(), imp->m_options, &params, &ec);
if (BOOST_LIKELY(!ec))
direntry_handle.handle = INVALID_HANDLE_VALUE;
#else
directory_iterator next(dir_it->path(), imp->m_options, ec);
#endif
if (BOOST_LIKELY(!ec))
{
#if defined(BOOST_WINDOWS_API)
direntry_handle.handle = INVALID_HANDLE_VALUE;
#endif
if (!next.is_end())
{
imp->m_stack.push_back(std::move(next)); // may throw

View File

@ -118,6 +118,8 @@
#endif // defined(linux) || defined(__linux) || defined(__linux__)
#include <boost/scope/unique_fd.hpp>
#if defined(POSIX_FADV_SEQUENTIAL) && (!defined(__ANDROID__) || __ANDROID_API__ >= 21)
#define BOOST_FILESYSTEM_HAS_POSIX_FADVISE
#endif
@ -305,26 +307,45 @@ bool not_found_error(int errval) noexcept; // forward declaration
// //
//--------------------------------------------------------------------------------------//
struct fd_wrapper
{
int fd;
fd_wrapper() noexcept : fd(-1) {}
explicit fd_wrapper(int fd) noexcept : fd(fd) {}
~fd_wrapper() noexcept
{
if (fd >= 0)
close_fd(fd);
}
fd_wrapper(fd_wrapper const&) = delete;
fd_wrapper& operator=(fd_wrapper const&) = delete;
};
inline bool not_found_error(int errval) noexcept
{
return errval == ENOENT || errval == ENOTDIR;
}
/*!
* Closes a file descriptor and returns the result, similar to close(2). Unlike close(2), guarantees that the file descriptor is closed even if EINTR error happens.
*
* Some systems don't close the file descriptor in case if the thread is interrupted by a signal and close(2) returns EINTR.
* Other (most) systems do close the file descriptor even when when close(2) returns EINTR, and attempting to close it
* again could close a different file descriptor that was opened by a different thread. This function hides this difference in behavior.
*
* Future POSIX standards will likely fix this by introducing posix_close (see https://www.austingroupbugs.net/view.php?id=529)
* and prohibiting returning EINTR from close(2), but we still have to support older systems where this new behavior is not available and close(2)
* behaves differently between systems.
*/
inline int close_fd(int fd)
{
#if defined(hpux) || defined(_hpux) || defined(__hpux)
int res;
while (true)
{
res = ::close(fd);
if (BOOST_UNLIKELY(res < 0))
{
int err = errno;
if (err == EINTR)
continue;
}
break;
}
return res;
#else
return ::close(fd);
#endif
}
#if defined(BOOST_FILESYSTEM_HAS_STATX)
//! A wrapper for statx libc function. Disable MSAN since at least on clang 10 it doesn't
@ -1108,19 +1129,12 @@ uintmax_t remove_all_impl
)
{
#if defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW) && defined(BOOST_FILESYSTEM_HAS_POSIX_AT_APIS)
fs::detail::directory_iterator_params params;
params.basedir_fd = parentdir_fd;
params.iterator_fd = -1;
fs::path remove_path;
fs::path filename;
const fs::path* remove_path = &p;
if (parentdir_fd != AT_FDCWD)
{
params.basedir = p.parent_path();
remove_path = path_algorithms::filename_v4(p);
}
else
{
remove_path = p;
filename = path_algorithms::filename_v4(p);
remove_path = &filename;
}
#endif
@ -1131,7 +1145,7 @@ uintmax_t remove_all_impl
{
error_code local_ec;
#if defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW) && defined(BOOST_FILESYSTEM_HAS_POSIX_AT_APIS)
type = fs::detail::symlink_status_impl(remove_path, &local_ec, parentdir_fd).type();
type = fs::detail::symlink_status_impl(*remove_path, &local_ec, parentdir_fd).type();
#else
type = fs::detail::symlink_status_impl(p, &local_ec).type();
#endif
@ -1154,9 +1168,27 @@ uintmax_t remove_all_impl
{
fs::directory_iterator itr;
#if defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW) && defined(BOOST_FILESYSTEM_HAS_POSIX_AT_APIS)
fs::detail::directory_iterator_construct(itr, remove_path, directory_options::_detail_no_follow, &params, &dit_create_ec);
fs::detail::directory_iterator_params params{ fs::detail::openat_directory(parentdir_fd, *remove_path, directory_options::_detail_no_follow, dit_create_ec) };
int dir_fd = -1;
if (BOOST_LIKELY(!dit_create_ec))
{
// Save dir_fd as constructing the iterator will move the fd into the iterator context
dir_fd = params.dir_fd.get();
fs::detail::directory_iterator_construct(itr, *remove_path, directory_options::_detail_no_follow, &params, &dit_create_ec);
}
#else
fs::detail::directory_iterator_construct(itr, p, directory_options::_detail_no_follow, nullptr, &dit_create_ec);
fs::detail::directory_iterator_construct
(
itr,
p,
#if defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW)
directory_options::_detail_no_follow,
#else
directory_options::none,
#endif
nullptr,
&dit_create_ec
);
#endif
if (BOOST_UNLIKELY(!!dit_create_ec))
@ -1188,7 +1220,7 @@ uintmax_t remove_all_impl
itr->path(),
ec
#if defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW) && defined(BOOST_FILESYSTEM_HAS_POSIX_AT_APIS)
, params.iterator_fd
, dir_fd
#endif
);
if (ec && *ec)
@ -1201,7 +1233,7 @@ uintmax_t remove_all_impl
}
#if defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW) && defined(BOOST_FILESYSTEM_HAS_POSIX_AT_APIS)
count += fs::detail::remove_impl(remove_path, type, ec, parentdir_fd);
count += fs::detail::remove_impl(*remove_path, type, ec, parentdir_fd);
#else
count += fs::detail::remove_impl(p, type, ec);
#endif
@ -2997,13 +3029,13 @@ bool copy_file(path const& from, path const& to, copy_options options, error_cod
int err = 0;
// Note: Declare fd_wrappers here so that errno is not clobbered by close() that may be called in fd_wrapper destructors
fd_wrapper infile, outfile;
// Note: Declare fd wrappers here so that errno is not clobbered by close() that may be called in fd wrapper destructors
boost::scope::unique_fd infile, outfile;
while (true)
{
infile.fd = ::open(from.c_str(), O_RDONLY | O_CLOEXEC);
if (BOOST_UNLIKELY(infile.fd < 0))
infile.reset(::open(from.c_str(), O_RDONLY | O_CLOEXEC));
if (BOOST_UNLIKELY(!infile))
{
err = errno;
if (err == EINTR)
@ -3023,7 +3055,7 @@ bool copy_file(path const& from, path const& to, copy_options options, error_cod
statx_data_mask |= STATX_MTIME;
struct ::statx from_stat;
if (BOOST_UNLIKELY(invoke_statx(infile.fd, "", AT_EMPTY_PATH | AT_NO_AUTOMOUNT, statx_data_mask, &from_stat) < 0))
if (BOOST_UNLIKELY(invoke_statx(infile.get(), "", AT_EMPTY_PATH | AT_NO_AUTOMOUNT, statx_data_mask, &from_stat) < 0))
{
fail_errno:
err = errno;
@ -3037,7 +3069,7 @@ bool copy_file(path const& from, path const& to, copy_options options, error_cod
}
#else
struct ::stat from_stat;
if (BOOST_UNLIKELY(::fstat(infile.fd, &from_stat) != 0))
if (BOOST_UNLIKELY(::fstat(infile.get(), &from_stat) != 0))
{
fail_errno:
err = errno;
@ -3065,8 +3097,8 @@ bool copy_file(path const& from, path const& to, copy_options options, error_cod
// Try opening the existing file without truncation to test the modification time later
while (true)
{
outfile.fd = ::open(to.c_str(), oflag, to_mode);
if (outfile.fd < 0)
outfile.reset(::open(to.c_str(), oflag, to_mode));
if (!outfile)
{
err = errno;
if (err == EINTR)
@ -3094,8 +3126,8 @@ bool copy_file(path const& from, path const& to, copy_options options, error_cod
while (true)
{
outfile.fd = ::open(to.c_str(), oflag, to_mode);
if (outfile.fd < 0)
outfile.reset(::open(to.c_str(), oflag, to_mode));
if (!outfile)
{
err = errno;
if (err == EINTR)
@ -3120,7 +3152,7 @@ bool copy_file(path const& from, path const& to, copy_options options, error_cod
}
struct ::statx to_stat;
if (BOOST_UNLIKELY(invoke_statx(outfile.fd, "", AT_EMPTY_PATH | AT_NO_AUTOMOUNT, statx_data_mask, &to_stat) < 0))
if (BOOST_UNLIKELY(invoke_statx(outfile.get(), "", AT_EMPTY_PATH | AT_NO_AUTOMOUNT, statx_data_mask, &to_stat) < 0))
goto fail_errno;
if (BOOST_UNLIKELY((to_stat.stx_mask & statx_data_mask) != statx_data_mask))
@ -3130,7 +3162,7 @@ bool copy_file(path const& from, path const& to, copy_options options, error_cod
}
#else
struct ::stat to_stat;
if (BOOST_UNLIKELY(::fstat(outfile.fd, &to_stat) != 0))
if (BOOST_UNLIKELY(::fstat(outfile.get(), &to_stat) != 0))
goto fail_errno;
#endif
@ -3163,12 +3195,12 @@ bool copy_file(path const& from, path const& to, copy_options options, error_cod
return false;
#endif
if (BOOST_UNLIKELY(::ftruncate(outfile.fd, 0) != 0))
if (BOOST_UNLIKELY(::ftruncate(outfile.get(), 0) != 0))
goto fail_errno;
}
// Note: Use block size of the target file since it is most important for writing performance.
err = filesystem::detail::atomic_load_relaxed(filesystem::detail::copy_file_data)(infile.fd, outfile.fd, get_size(from_stat), get_blksize(to_stat));
err = filesystem::detail::atomic_load_relaxed(filesystem::detail::copy_file_data)(infile.get(), outfile.get(), get_size(from_stat), get_blksize(to_stat));
if (BOOST_UNLIKELY(err != 0))
goto fail; // err already contains the error code
@ -3177,7 +3209,7 @@ bool copy_file(path const& from, path const& to, copy_options options, error_cod
// we may need to update its mode bits to match the source file.
if ((to_mode & fs::perms_mask) != (from_mode & fs::perms_mask))
{
if (BOOST_UNLIKELY(::fchmod(outfile.fd, (from_mode & fs::perms_mask)) != 0 &&
if (BOOST_UNLIKELY(::fchmod(outfile.get(), (from_mode & fs::perms_mask)) != 0 &&
(options & copy_options::ignore_attribute_errors) == copy_options::none))
{
goto fail_errno;
@ -3188,9 +3220,9 @@ bool copy_file(path const& from, path const& to, copy_options options, error_cod
if ((options & (copy_options::synchronize_data | copy_options::synchronize)) != copy_options::none)
{
if ((options & copy_options::synchronize) != copy_options::none)
err = full_sync(outfile.fd);
err = full_sync(outfile.get());
else
err = data_sync(outfile.fd);
err = data_sync(outfile.get());
if (BOOST_UNLIKELY(err != 0))
goto fail;
@ -3198,8 +3230,8 @@ bool copy_file(path const& from, path const& to, copy_options options, error_cod
// We have to explicitly close the output file descriptor in order to handle a possible error returned from it. The error may indicate
// a failure of a prior write operation.
err = close_fd(outfile.fd);
outfile.fd = -1;
err = close_fd(outfile.get());
outfile.release();
if (BOOST_UNLIKELY(err < 0))
{
err = errno;

View File

@ -1,6 +1,6 @@
// posix_tools.hpp -------------------------------------------------------------------//
// Copyright 2021 Andrey Semashev
// Copyright 2021-2024 Andrey Semashev
// Distributed under the Boost Software License, Version 1.0.
// See http://www.boost.org/LICENSE_1_0.txt
@ -13,16 +13,15 @@
#define BOOST_FILESYSTEM_SRC_POSIX_TOOLS_HPP_
#include "platform_config.hpp"
#include <cerrno>
#include <boost/filesystem/config.hpp>
#ifdef BOOST_HAS_UNISTD_H
#include <unistd.h>
#endif
#include <fcntl.h>
#include <boost/scope/unique_fd.hpp>
#include <boost/system/error_code.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/file_status.hpp>
#include <boost/filesystem/directory.hpp>
#include <boost/filesystem/detail/header.hpp> // must be the last #include
namespace boost {
@ -32,52 +31,12 @@ namespace detail {
//! Platform-specific parameters for directory iterator construction
struct directory_iterator_params
{
#if defined(BOOST_FILESYSTEM_HAS_POSIX_AT_APIS)
//! Base directory path, relative to which to interpret relative paths. Must be empty if basedir_fd is AT_FDCWD.
path basedir;
//! File descriptor of the base directory relative to which to interpret relative paths
int basedir_fd;
#endif
#if defined(BOOST_FILESYSTEM_HAS_FDOPENDIR_NOFOLLOW)
//! File descriptor of the directory over which the iterator iterates
int iterator_fd;
//! File descriptor of the directory to iterate over. If not a negative value, the directory path is only used to generate paths returned by the iterator.
boost::scope::unique_fd dir_fd;
#endif
};
/*!
* Closes a file descriptor and returns the result, similar to close(2). Unlike close(2), guarantees that the file descriptor is closed even if EINTR error happens.
*
* Some systems don't close the file descriptor in case if the thread is interrupted by a signal and close(2) returns EINTR.
* Other (most) systems do close the file descriptor even when when close(2) returns EINTR, and attempting to close it
* again could close a different file descriptor that was opened by a different thread. This function hides this difference in behavior.
*
* Future POSIX standards will likely fix this by introducing posix_close (see https://www.austingroupbugs.net/view.php?id=529)
* and prohibiting returning EINTR from close(2), but we still have to support older systems where this new behavior is not available and close(2)
* behaves differently between systems.
*/
inline int close_fd(int fd)
{
#if defined(hpux) || defined(_hpux) || defined(__hpux)
int res;
while (true)
{
res = ::close(fd);
if (BOOST_UNLIKELY(res < 0))
{
int err = errno;
if (err == EINTR)
continue;
}
break;
}
return res;
#else
return ::close(fd);
#endif
}
//! status() implementation
file_status status_impl
(
@ -98,6 +57,18 @@ file_status symlink_status_impl
#endif
);
#if defined(BOOST_POSIX_API)
//! Opens a directory file and returns a file descriptor. Returns a negative value in case of error.
boost::scope::unique_fd open_directory(path const& p, directory_options opts, system::error_code& ec);
#if defined(BOOST_FILESYSTEM_HAS_POSIX_AT_APIS)
//! Opens a directory file and returns a file descriptor. Returns a negative value in case of error.
boost::scope::unique_fd openat_directory(int basedir_fd, path const& p, directory_options opts, system::error_code& ec);
#endif // defined(BOOST_FILESYSTEM_HAS_POSIX_AT_APIS)
#endif // defined(BOOST_POSIX_API)
} // namespace detail
} // namespace filesystem
} // namespace boost

View File

@ -1,7 +1,7 @@
// filesystem unique_path.cpp --------------------------------------------------------//
// Copyright Beman Dawes 2010
// Copyright Andrey Semashev 2020
// Copyright Andrey Semashev 2020, 2024
// Distributed under the Boost Software License, Version 1.0.
// See http://www.boost.org/LICENSE_1_0.txt
@ -20,10 +20,8 @@
#include <cerrno>
#include <stddef.h>
#include <fcntl.h>
#ifdef BOOST_HAS_UNISTD_H
#include <unistd.h>
#endif
#include <fcntl.h>
#if !defined(BOOST_FILESYSTEM_DISABLE_ARC4RANDOM)
#if BOOST_OS_BSD_OPEN >= BOOST_VERSION_NUMBER(2, 1, 0) || \
@ -56,6 +54,7 @@
#endif // (defined(__linux__) || defined(__linux) || defined(linux)) && (!defined(__ANDROID__) || __ANDROID_API__ >= 28)
#endif // !defined(BOOST_FILESYSTEM_DISABLE_GETRANDOM)
#include <boost/scope/unique_fd.hpp>
#include "posix_tools.hpp"
#else // BOOST_WINDOWS_API
@ -115,31 +114,52 @@ namespace {
//! Fills buffer with cryptographically random data obtained from /dev/(u)random
int fill_random_dev_random(void* buf, std::size_t len)
{
int file = ::open("/dev/urandom", O_RDONLY | O_CLOEXEC);
if (file == -1)
boost::scope::unique_fd file;
while (true)
{
file = ::open("/dev/random", O_RDONLY | O_CLOEXEC);
if (file == -1)
return errno;
file.reset(::open("/dev/urandom", O_RDONLY | O_CLOEXEC));
if (!file)
{
if (errno == EINTR)
continue;
}
break;
}
if (!file)
{
while (true)
{
file.reset(::open("/dev/random", O_RDONLY | O_CLOEXEC));
if (!file)
{
const int err = errno;
if (err == EINTR)
continue;
return err;
}
break;
}
}
std::size_t bytes_read = 0u;
while (bytes_read < len)
{
ssize_t n = ::read(file, buf, len - bytes_read);
if (BOOST_UNLIKELY(n == -1))
ssize_t n = ::read(file.get(), buf, len - bytes_read);
if (BOOST_UNLIKELY(n < 0))
{
int err = errno;
const int err = errno;
if (err == EINTR)
continue;
close_fd(file);
return err;
}
bytes_read += n;
buf = static_cast< char* >(buf) + n;
}
close_fd(file);
return 0;
}