// Filesystem directory iterator utilities -*- C++ -*-

// Copyright (C) 2014-2025 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, [[maybe_unused]] 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
