ebf6175464
This fixes the remaining filesystem::remove_all race condition by using POSIX openat to recurse into sub-directories and using POSIX unlinkat to remove files. This avoids the remaining race where the directory being removed is replaced with a symlink after the directory has been opened, so that the filesystem::remove("subdir/file") resolves to "target/file" instead, because "subdir" has been removed and replaced with a symlink. The previous patch only fixed the case where the directory was replaced with a symlink before we tried to open it, but it still used the full (potentially compromised) path as an argument to filesystem::remove. The first part of the fix is to use openat when recursing into a sub-directory with recursive_directory_iterator. This means that opening "dir/subdir" uses the file descriptor for "dir", and so is sure to open "dir/subdir" and not "symlink/subdir". (The previous patch to use O_NOFOLLOW already ensured we won't open "dir/symlink/" here.) The second part of the fix is to use unlinkat for the remove_all operation. Previously we used a directory_iterator to get the name of each file in a directory and then used filesystem::remove(iter->path()) on that name. This meant that any checks (e.g. O_NOFOLLOW) done by the iterator could be invalidated before the remove operation on that pathname. The directory iterator contains an open DIR stream, which we can use to obtain a file descriptor to pass to unlinkat. This ensures that the file being deleted really is contained within the directory we're iterating over, rather than using a pathname that could resolve to some other file. The filesystem::remove_all function previously used a (non-recursive) filesystem::directory_iterator for each directory, and called itself recursively for sub-directories. The new implementation uses a single filesystem::recursive_directory_iterator object, and calls a new __erase member function on that iterator. That new __erase member function does the actual work of removing a file (or a directory after its contents have been iterated over and removed) using unlinkat. That means we don't need to expose the DIR stream or its file descriptor to the remove_all function, it's still encapuslated by the iterator class. It would be possible to add a __rewind member to directory iterators too, to call rewinddir after each modification to the directory. That would make it more likely for filesystem::remove_all to successfully remove everything even if files are being written to the directory tree while removing it. It's unclear if that is actually prefereable, or if it's better to fail and report an error at the first opportunity. The necessary APIs (openat, unlinkat, fdopendir, dirfd) are defined in POSIX.1-2008, and in Glibc since 2.10. But if the target doesn't provide them, the original code (with race conditions) is still used. This also reduces the number of small memory allocations needed for std::filesystem::remove_all, because we do not store the full path to every directory entry that is iterated over. The new filename_only option means we only store the filename in the directory entry, as that is all we need in order to use openat or unlinkat. Finally, rather than duplicating everything for the Filesystem TS, the std::experimental::filesystem::remove_all implementation now just calls std::filesystem::remove_all to do the work. libstdc++-v3/ChangeLog: PR libstdc++/104161 * acinclude.m4 (GLIBCXX_CHECK_FILESYSTEM_DEPS): Check for dirfd and unlinkat. * config.h.in: Regenerate. * configure: Regenerate. * include/bits/fs_dir.h (recursive_directory_iterator): Declare remove_all overloads as friends. (recursive_directory_iterator::__erase): Declare new member function. * include/bits/fs_fwd.h (remove, remove_all): Declare. * src/c++17/fs_dir.cc (_Dir): Add filename_only parameter to constructor. Pass file descriptor argument to base constructor. (_Dir::dir_and_pathname, _Dir::open_subdir, _Dir::do_unlink) (_Dir::unlink, _Dir::rmdir): Define new member functions. (directory_iterator): Pass filename_only argument to _Dir constructor. (recursive_directory_iterator::_Dir_stack): Adjust constructor parameters to take a _Dir rvalue instead of creating one. (_Dir_stack::orig): Add data member for storing original path. (_Dir_stack::report_error): Define new member function. (__directory_iterator_nofollow): Move here from dir-common.h and fix value to be a power of two. (__directory_iterator_filename_only): Define new constant. (recursive_directory_iterator): Construct _Dir object and move into _M_dirs stack. Pass skip_permission_denied argument to first advance call. (recursive_directory_iterator::increment): Use _Dir::open_subdir. (recursive_directory_iterator::__erase): Define new member function. * src/c++17/fs_ops.cc (ErrorReporter, do_remove_all): Remove. (fs::remove_all): Use new recursive_directory_iterator::__erase member function. * src/filesystem/dir-common.h (_Dir_base): Add int parameter to constructor and use openat to implement nofollow semantics. (_Dir_base::fdcwd, _Dir_base::set_close_on_exec, _Dir_base::openat): Define new member functions. (__directory_iterator_nofollow): Move to fs_dir.cc. * src/filesystem/dir.cc (_Dir): Pass file descriptor argument to base constructor. (_Dir::dir_and_pathname, _Dir::open_subdir): Define new member functions. (recursive_directory_iterator::_Dir_stack): Adjust constructor parameters to take a _Dir rvalue instead of creating one. (recursive_directory_iterator): Check for new nofollow option. Construct _Dir object and move into _M_dirs stack. Pass skip_permission_denied argument to first advance call. (recursive_directory_iterator::increment): Use _Dir::open_subdir. * src/filesystem/ops.cc (fs::remove_all): Use C++17 remove_all.
367 lines
11 KiB
C++
367 lines
11 KiB
C++
// Filesystem declarations -*- C++ -*-
|
|
|
|
// Copyright (C) 2014-2022 Free Software Foundation, Inc.
|
|
//
|
|
// This file is part of the GNU ISO C++ Library. This library is free
|
|
// software; you can redistribute it and/or modify it under the
|
|
// terms of the GNU General Public License as published by the
|
|
// Free Software Foundation; either version 3, or (at your option)
|
|
// any later version.
|
|
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// Under Section 7 of GPL version 3, you are granted additional
|
|
// permissions described in the GCC Runtime Library Exception, version
|
|
// 3.1, as published by the Free Software Foundation.
|
|
|
|
// You should have received a copy of the GNU General Public License and
|
|
// a copy of the GCC Runtime Library Exception along with this program;
|
|
// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
|
|
// <http://www.gnu.org/licenses/>.
|
|
|
|
/** @file include/bits/fs_fwd.h
|
|
* This is an internal header file, included by other library headers.
|
|
* Do not attempt to use it directly. @headername{filesystem}
|
|
*/
|
|
|
|
#ifndef _GLIBCXX_FS_FWD_H
|
|
#define _GLIBCXX_FS_FWD_H 1
|
|
|
|
#if __cplusplus >= 201703L
|
|
|
|
#include <system_error>
|
|
#include <cstdint>
|
|
#include <bits/chrono.h>
|
|
|
|
namespace std _GLIBCXX_VISIBILITY(default)
|
|
{
|
|
_GLIBCXX_BEGIN_NAMESPACE_VERSION
|
|
|
|
/// ISO C++ 2017 namespace for File System library
|
|
namespace filesystem
|
|
{
|
|
#if _GLIBCXX_USE_CXX11_ABI
|
|
/// @cond undocumented
|
|
inline namespace __cxx11 __attribute__((__abi_tag__ ("cxx11"))) { }
|
|
/// @endcond
|
|
#endif
|
|
|
|
/** @addtogroup filesystem
|
|
* @{
|
|
*/
|
|
|
|
class file_status;
|
|
_GLIBCXX_BEGIN_NAMESPACE_CXX11
|
|
class path;
|
|
class filesystem_error;
|
|
class directory_entry;
|
|
class directory_iterator;
|
|
class recursive_directory_iterator;
|
|
_GLIBCXX_END_NAMESPACE_CXX11
|
|
|
|
/// Information about free space on a disk
|
|
struct space_info
|
|
{
|
|
uintmax_t capacity;
|
|
uintmax_t free;
|
|
uintmax_t available;
|
|
|
|
#if __cpp_impl_three_way_comparison >= 201907L
|
|
friend bool operator==(const space_info&, const space_info&) = default;
|
|
#endif
|
|
};
|
|
|
|
/// Enumerated type representing the type of a file
|
|
enum class file_type : signed char {
|
|
none = 0, not_found = -1, regular = 1, directory = 2, symlink = 3,
|
|
block = 4, character = 5, fifo = 6, socket = 7, unknown = 8
|
|
};
|
|
|
|
/// Bitmask type controlling effects of `filesystem::copy`
|
|
enum class copy_options : unsigned short {
|
|
none = 0,
|
|
skip_existing = 1, overwrite_existing = 2, update_existing = 4,
|
|
recursive = 8,
|
|
copy_symlinks = 16, skip_symlinks = 32,
|
|
directories_only = 64, create_symlinks = 128, create_hard_links = 256
|
|
};
|
|
|
|
/// @{
|
|
/// @relates copy_options
|
|
constexpr copy_options
|
|
operator&(copy_options __x, copy_options __y) noexcept
|
|
{
|
|
using __utype = typename std::underlying_type<copy_options>::type;
|
|
return static_cast<copy_options>(
|
|
static_cast<__utype>(__x) & static_cast<__utype>(__y));
|
|
}
|
|
|
|
constexpr copy_options
|
|
operator|(copy_options __x, copy_options __y) noexcept
|
|
{
|
|
using __utype = typename std::underlying_type<copy_options>::type;
|
|
return static_cast<copy_options>(
|
|
static_cast<__utype>(__x) | static_cast<__utype>(__y));
|
|
}
|
|
|
|
constexpr copy_options
|
|
operator^(copy_options __x, copy_options __y) noexcept
|
|
{
|
|
using __utype = typename std::underlying_type<copy_options>::type;
|
|
return static_cast<copy_options>(
|
|
static_cast<__utype>(__x) ^ static_cast<__utype>(__y));
|
|
}
|
|
|
|
constexpr copy_options
|
|
operator~(copy_options __x) noexcept
|
|
{
|
|
using __utype = typename std::underlying_type<copy_options>::type;
|
|
return static_cast<copy_options>(~static_cast<__utype>(__x));
|
|
}
|
|
|
|
inline copy_options&
|
|
operator&=(copy_options& __x, copy_options __y) noexcept
|
|
{ return __x = __x & __y; }
|
|
|
|
inline copy_options&
|
|
operator|=(copy_options& __x, copy_options __y) noexcept
|
|
{ return __x = __x | __y; }
|
|
|
|
inline copy_options&
|
|
operator^=(copy_options& __x, copy_options __y) noexcept
|
|
{ return __x = __x ^ __y; }
|
|
/// @}
|
|
|
|
|
|
/// Bitmask type representing file access permissions
|
|
enum class perms : unsigned {
|
|
none = 0,
|
|
owner_read = 0400,
|
|
owner_write = 0200,
|
|
owner_exec = 0100,
|
|
owner_all = 0700,
|
|
group_read = 040,
|
|
group_write = 020,
|
|
group_exec = 010,
|
|
group_all = 070,
|
|
others_read = 04,
|
|
others_write = 02,
|
|
others_exec = 01,
|
|
others_all = 07,
|
|
all = 0777,
|
|
set_uid = 04000,
|
|
set_gid = 02000,
|
|
sticky_bit = 01000,
|
|
mask = 07777,
|
|
unknown = 0xFFFF,
|
|
};
|
|
|
|
/// @{
|
|
/// @relates perms
|
|
constexpr perms
|
|
operator&(perms __x, perms __y) noexcept
|
|
{
|
|
using __utype = typename std::underlying_type<perms>::type;
|
|
return static_cast<perms>(
|
|
static_cast<__utype>(__x) & static_cast<__utype>(__y));
|
|
}
|
|
|
|
constexpr perms
|
|
operator|(perms __x, perms __y) noexcept
|
|
{
|
|
using __utype = typename std::underlying_type<perms>::type;
|
|
return static_cast<perms>(
|
|
static_cast<__utype>(__x) | static_cast<__utype>(__y));
|
|
}
|
|
|
|
constexpr perms
|
|
operator^(perms __x, perms __y) noexcept
|
|
{
|
|
using __utype = typename std::underlying_type<perms>::type;
|
|
return static_cast<perms>(
|
|
static_cast<__utype>(__x) ^ static_cast<__utype>(__y));
|
|
}
|
|
|
|
constexpr perms
|
|
operator~(perms __x) noexcept
|
|
{
|
|
using __utype = typename std::underlying_type<perms>::type;
|
|
return static_cast<perms>(~static_cast<__utype>(__x));
|
|
}
|
|
|
|
inline perms&
|
|
operator&=(perms& __x, perms __y) noexcept
|
|
{ return __x = __x & __y; }
|
|
|
|
inline perms&
|
|
operator|=(perms& __x, perms __y) noexcept
|
|
{ return __x = __x | __y; }
|
|
|
|
inline perms&
|
|
operator^=(perms& __x, perms __y) noexcept
|
|
{ return __x = __x ^ __y; }
|
|
/// @}
|
|
|
|
/// Bitmask type controlling changes to permissions
|
|
enum class perm_options : unsigned {
|
|
replace = 0x1,
|
|
add = 0x2,
|
|
remove = 0x4,
|
|
nofollow = 0x8
|
|
};
|
|
|
|
/// @{
|
|
/// @relates perm_options
|
|
constexpr perm_options
|
|
operator&(perm_options __x, perm_options __y) noexcept
|
|
{
|
|
using __utype = typename std::underlying_type<perm_options>::type;
|
|
return static_cast<perm_options>(
|
|
static_cast<__utype>(__x) & static_cast<__utype>(__y));
|
|
}
|
|
|
|
constexpr perm_options
|
|
operator|(perm_options __x, perm_options __y) noexcept
|
|
{
|
|
using __utype = typename std::underlying_type<perm_options>::type;
|
|
return static_cast<perm_options>(
|
|
static_cast<__utype>(__x) | static_cast<__utype>(__y));
|
|
}
|
|
|
|
constexpr perm_options
|
|
operator^(perm_options __x, perm_options __y) noexcept
|
|
{
|
|
using __utype = typename std::underlying_type<perm_options>::type;
|
|
return static_cast<perm_options>(
|
|
static_cast<__utype>(__x) ^ static_cast<__utype>(__y));
|
|
}
|
|
|
|
constexpr perm_options
|
|
operator~(perm_options __x) noexcept
|
|
{
|
|
using __utype = typename std::underlying_type<perm_options>::type;
|
|
return static_cast<perm_options>(~static_cast<__utype>(__x));
|
|
}
|
|
|
|
inline perm_options&
|
|
operator&=(perm_options& __x, perm_options __y) noexcept
|
|
{ return __x = __x & __y; }
|
|
|
|
inline perm_options&
|
|
operator|=(perm_options& __x, perm_options __y) noexcept
|
|
{ return __x = __x | __y; }
|
|
|
|
inline perm_options&
|
|
operator^=(perm_options& __x, perm_options __y) noexcept
|
|
{ return __x = __x ^ __y; }
|
|
/// @}
|
|
|
|
/// Bitmask type controlling directory iteration
|
|
enum class directory_options : unsigned char {
|
|
none = 0, follow_directory_symlink = 1, skip_permission_denied = 2
|
|
};
|
|
|
|
/// @{
|
|
/// @relates directory_options
|
|
constexpr directory_options
|
|
operator&(directory_options __x, directory_options __y) noexcept
|
|
{
|
|
using __utype = typename std::underlying_type<directory_options>::type;
|
|
return static_cast<directory_options>(
|
|
static_cast<__utype>(__x) & static_cast<__utype>(__y));
|
|
}
|
|
|
|
constexpr directory_options
|
|
operator|(directory_options __x, directory_options __y) noexcept
|
|
{
|
|
using __utype = typename std::underlying_type<directory_options>::type;
|
|
return static_cast<directory_options>(
|
|
static_cast<__utype>(__x) | static_cast<__utype>(__y));
|
|
}
|
|
|
|
constexpr directory_options
|
|
operator^(directory_options __x, directory_options __y) noexcept
|
|
{
|
|
using __utype = typename std::underlying_type<directory_options>::type;
|
|
return static_cast<directory_options>(
|
|
static_cast<__utype>(__x) ^ static_cast<__utype>(__y));
|
|
}
|
|
|
|
constexpr directory_options
|
|
operator~(directory_options __x) noexcept
|
|
{
|
|
using __utype = typename std::underlying_type<directory_options>::type;
|
|
return static_cast<directory_options>(~static_cast<__utype>(__x));
|
|
}
|
|
|
|
inline directory_options&
|
|
operator&=(directory_options& __x, directory_options __y) noexcept
|
|
{ return __x = __x & __y; }
|
|
|
|
inline directory_options&
|
|
operator|=(directory_options& __x, directory_options __y) noexcept
|
|
{ return __x = __x | __y; }
|
|
|
|
inline directory_options&
|
|
operator^=(directory_options& __x, directory_options __y) noexcept
|
|
{ return __x = __x ^ __y; }
|
|
/// @}
|
|
|
|
/// The type used for file timestamps
|
|
using file_time_type = __file_clock::time_point;
|
|
|
|
// operational functions
|
|
|
|
void copy(const path& __from, const path& __to, copy_options __options);
|
|
void copy(const path& __from, const path& __to, copy_options __options,
|
|
error_code&);
|
|
|
|
bool copy_file(const path& __from, const path& __to, copy_options __option);
|
|
bool copy_file(const path& __from, const path& __to, copy_options __option,
|
|
error_code&);
|
|
|
|
path current_path();
|
|
|
|
bool exists(file_status) noexcept;
|
|
|
|
bool is_other(file_status) noexcept;
|
|
|
|
uintmax_t file_size(const path&);
|
|
uintmax_t file_size(const path&, error_code&) noexcept;
|
|
uintmax_t hard_link_count(const path&);
|
|
uintmax_t hard_link_count(const path&, error_code&) noexcept;
|
|
file_time_type last_write_time(const path&);
|
|
file_time_type last_write_time(const path&, error_code&) noexcept;
|
|
|
|
void permissions(const path&, perms, perm_options, error_code&) noexcept;
|
|
|
|
path proximate(const path& __p, const path& __base, error_code& __ec);
|
|
path proximate(const path& __p, const path& __base, error_code& __ec);
|
|
|
|
path relative(const path& __p, const path& __base, error_code& __ec);
|
|
|
|
file_status status(const path&);
|
|
file_status status(const path&, error_code&) noexcept;
|
|
|
|
bool status_known(file_status) noexcept;
|
|
|
|
file_status symlink_status(const path&);
|
|
file_status symlink_status(const path&, error_code&) noexcept;
|
|
|
|
bool is_regular_file(file_status) noexcept;
|
|
bool is_symlink(file_status) noexcept;
|
|
|
|
bool remove(const path&, error_code&) noexcept;
|
|
uintmax_t remove_all(const path&);
|
|
uintmax_t remove_all(const path&, error_code&);
|
|
|
|
/// @}
|
|
} // namespace filesystem
|
|
_GLIBCXX_END_NAMESPACE_VERSION
|
|
} // namespace std
|
|
#endif // C++17
|
|
#endif // _GLIBCXX_FS_FWD_H
|