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

// Copyright (C) 2014-2020 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 <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>
#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; }
#endif
} // namespace __gnu_posix

namespace posix = __gnu_posix;

struct _Dir_base
{
  _Dir_base(posix::DIR* dirp = nullptr) : dirp(dirp) { }

  // If no error occurs then dirp is non-null,
  // otherwise null (whether error ignored or not).
  _Dir_base(const posix::char_type* pathname, bool skip_permission_denied,
	    error_code& ec) noexcept
  : dirp(posix::opendir(pathname))
  {
    if (dirp)
      ec.clear();
    else
    {
      const int err = errno;
      if (err == EACCES && skip_permission_denied)
	ec.clear();
      else
	ec.assign(err, 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

  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
