blob: 228fab55afbcf1c391fe81705721ca3e0639ceed [file] [log] [blame]
// Filesystem directory iterator utilities -*- 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/>.
#ifndef _GLIBCXX_DIR_COMMON_H
#define _GLIBCXX_DIR_COMMON_H 1
#include <stdint.h> // uint32_t
#include <string.h> // strcmp
#include <errno.h>
#if _GLIBCXX_FILESYSTEM_IS_WINDOWS
#include <wchar.h> // wcscmp
#endif
#ifdef _GLIBCXX_HAVE_DIRENT_H
# ifdef _GLIBCXX_HAVE_SYS_TYPES_H
# include <sys/types.h>
# endif
# include <dirent.h> // opendir, readdir, fdopendir, dirfd
# ifdef _GLIBCXX_HAVE_FCNTL_H
# include <fcntl.h> // open, openat, fcntl, AT_FDCWD, O_NOFOLLOW etc.
# include <unistd.h> // close, unlinkat
# endif
#endif
namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
namespace filesystem
{
namespace __gnu_posix
{
#if _GLIBCXX_FILESYSTEM_IS_WINDOWS
// Adapt the Windows _wxxx functions to look like POSIX xxx, but for wchar_t*.
using char_type = wchar_t;
using DIR = ::_WDIR;
using dirent = _wdirent;
inline DIR* opendir(const wchar_t* path) { return ::_wopendir(path); }
inline dirent* readdir(DIR* dir) { return ::_wreaddir(dir); }
inline int closedir(DIR* dir) { return ::_wclosedir(dir); }
#elif defined _GLIBCXX_HAVE_DIRENT_H
using char_type = char;
using DIR = ::DIR;
typedef struct ::dirent dirent;
using ::opendir;
using ::readdir;
using ::closedir;
#else
using char_type = char;
struct dirent { const char* d_name; };
struct DIR { };
inline DIR* opendir(const char*) { return nullptr; }
inline dirent* readdir(DIR*) { return nullptr; }
inline int closedir(DIR*) { return -1; }
#undef _GLIBCXX_HAVE_DIRFD
#undef _GLIBCXX_HAVE_UNLINKAT
#endif
} // namespace __gnu_posix
namespace posix = __gnu_posix;
inline bool
is_permission_denied_error(int e)
{
if (e == EACCES)
return true;
#ifdef __APPLE__
if (e == EPERM) // See PR 99533
return true;
#endif
return false;
}
struct _Dir_base
{
// As well as the full pathname (including the directory iterator's path)
// this type contains a file descriptor for a directory and a second pathname
// relative to that directory. The file descriptor and relative pathname
// can be used with POSIX openat and unlinkat.
struct _At_path
{
// No file descriptor given, so interpret the pathname relative to the CWD.
_At_path(const posix::char_type* p) noexcept
: pathname(p), dir_fd(fdcwd()), offset(0)
{ }
_At_path(int fd, const posix::char_type* p, size_t offset) noexcept
: pathname(p), dir_fd(fd), offset(offset)
{ }
const posix::char_type*
path() const noexcept { return pathname; }
int
dir() const noexcept { return dir_fd; }
const posix::char_type*
path_at_dir() const noexcept { return pathname + offset; }
private:
const posix::char_type* pathname; // Full path relative to CWD.
int dir_fd; // A directory descriptor (either the parent dir, or AT_FDCWD).
uint32_t offset; // Offset into pathname for the part relative to dir_fd.
// Special value representing the current working directory.
// Not a valid file descriptor for an open directory stream.
static constexpr int
fdcwd() noexcept
{
#ifdef AT_FDCWD
return AT_FDCWD;
#else
return -1; // Use invalid fd if AT_FDCWD isn't supported.
#endif
}
};
// If no error occurs then dirp is non-null,
// otherwise null (even if a permission denied error is ignored).
_Dir_base(const _At_path& atp,
bool skip_permission_denied, bool nofollow,
error_code& ec) noexcept
: dirp(_Dir_base::openat(atp, nofollow))
{
if (dirp)
ec.clear();
else if (is_permission_denied_error(errno) && skip_permission_denied)
ec.clear();
else
ec.assign(errno, std::generic_category());
}
_Dir_base(_Dir_base&& d) : dirp(std::exchange(d.dirp, nullptr)) { }
_Dir_base& operator=(_Dir_base&&) = delete;
~_Dir_base() { if (dirp) posix::closedir(dirp); }
const posix::dirent*
advance(bool skip_permission_denied, error_code& ec) noexcept
{
ec.clear();
int err = std::exchange(errno, 0);
const posix::dirent* entp = posix::readdir(dirp);
// std::swap cannot be used with Bionic's errno
err = std::exchange(errno, err);
if (entp)
{
// skip past dot and dot-dot
if (is_dot_or_dotdot(entp->d_name))
return advance(skip_permission_denied, ec);
return entp;
}
else if (err)
{
if (err == EACCES && skip_permission_denied)
return nullptr;
ec.assign(err, std::generic_category());
return nullptr;
}
else
{
// reached the end
return nullptr;
}
}
static bool is_dot_or_dotdot(const char* s) noexcept
{ return !strcmp(s, ".") || !strcmp(s, ".."); }
#if _GLIBCXX_FILESYSTEM_IS_WINDOWS
static bool is_dot_or_dotdot(const wchar_t* s) noexcept
{ return !wcscmp(s, L".") || !wcscmp(s, L".."); }
#endif
// Set the close-on-exec flag if not already done via O_CLOEXEC.
static bool
set_close_on_exec([[maybe_unused]] int fd)
{
#if ! defined O_CLOEXEC && defined FD_CLOEXEC
int flags = ::fcntl(fd, F_GETFD);
if (flags == -1 || ::fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
return false;
#endif
return true;
}
static posix::DIR*
openat(const _At_path& atp, bool nofollow)
{
#if _GLIBCXX_HAVE_FDOPENDIR && defined O_RDONLY && defined O_DIRECTORY \
&& ! _GLIBCXX_FILESYSTEM_IS_WINDOWS
// Any file descriptor we open here should be closed on exec.
#ifdef O_CLOEXEC
constexpr int close_on_exec = O_CLOEXEC;
#else
constexpr int close_on_exec = 0;
#endif
int flags = O_RDONLY | O_DIRECTORY | close_on_exec;
// Directory iterators are vulnerable to race conditions unless O_NOFOLLOW
// is supported, because a directory could be replaced with a symlink after
// checking is_directory(symlink_status(f)). O_NOFOLLOW avoids the race.
#ifdef O_NOFOLLOW
if (nofollow)
flags |= O_NOFOLLOW;
#else
nofollow = false;
#endif
int fd;
#if _GLIBCXX_HAVE_OPENAT
fd = ::openat(atp.dir(), atp.path_at_dir(), flags);
#else
// If we cannot use openat, there's no benefit to using posix::open unless
// we will use O_NOFOLLOW, so just use the simpler posix::opendir.
if (!nofollow)
return posix::opendir(atp.path());
fd = ::open(atp.path(), flags);
#endif
if (fd == -1)
return nullptr;
if (set_close_on_exec(fd))
if (::DIR* dirp = ::fdopendir(fd))
return dirp;
int err = errno;
::close(fd);
errno = err;
return nullptr;
#else
return posix::opendir(atp.path());
#endif
}
posix::DIR* dirp;
};
} // namespace filesystem
// BEGIN/END macros must be defined before including this file.
_GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM
inline file_type
get_file_type(const std::filesystem::__gnu_posix::dirent& d [[gnu::unused]])
{
#ifdef _GLIBCXX_HAVE_STRUCT_DIRENT_D_TYPE
switch (d.d_type)
{
case DT_BLK:
return file_type::block;
case DT_CHR:
return file_type::character;
case DT_DIR:
return file_type::directory;
case DT_FIFO:
return file_type::fifo;
case DT_LNK:
return file_type::symlink;
case DT_REG:
return file_type::regular;
case DT_SOCK:
return file_type::socket;
case DT_UNKNOWN:
return file_type::unknown;
default:
return file_type::none;
}
#else
return file_type::none;
#endif
}
_GLIBCXX_END_NAMESPACE_FILESYSTEM
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace std
#endif // _GLIBCXX_DIR_COMMON_H