//  operations.cpp  --------------------------------------------------------------------//

//  Copyright 2002-2009, 2014 Beman Dawes
//  Copyright 2001 Dietmar Kuehl
//  Copyright 2019 Andrey Semashev

//  Distributed under the Boost Software License, Version 1.0.
//  See http://www.boost.org/LICENSE_1_0.txt

//  See library home page at http://www.boost.org/libs/filesystem

//--------------------------------------------------------------------------------------//

#ifndef BOOST_SYSTEM_NO_DEPRECATED
# define BOOST_SYSTEM_NO_DEPRECATED
#endif

#ifndef _POSIX_PTHREAD_SEMANTICS
# define _POSIX_PTHREAD_SEMANTICS  // Sun readdir_r() needs this
#endif

// Include Boost.Predef first so that windows.h is guaranteed to be not included
#include <boost/predef/os/windows.h>
#if BOOST_OS_WINDOWS
#include <boost/winapi/config.hpp>
#endif

#include <boost/filesystem/directory.hpp>
#include <boost/filesystem/exception.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/file_status.hpp>

#include <cstddef>
#include <cerrno>
#include <cstring>
#include <cstdlib> // std::malloc, std::free
#include <new> // std::nothrow, std::bad_alloc
#include <limits>
#include <string>
#include <utility> // std::move
#include <boost/assert.hpp>
#include <boost/system/error_code.hpp>
#include <boost/smart_ptr/intrusive_ptr.hpp>

#ifdef BOOST_POSIX_API

#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>

#if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && (_POSIX_THREAD_SAFE_FUNCTIONS+0 >= 0)\
  && defined(_SC_THREAD_SAFE_FUNCTIONS)\
  && !defined(__CYGWIN__)\
  && !(defined(linux) || defined(__linux) || defined(__linux__))\
  && !defined(__ANDROID__)\
  && (!defined(__hpux) || defined(_REENTRANT)) \
  && (!defined(_AIX) || defined(__THREAD_SAFE))
#define BOOST_FILESYSTEM_USE_READDIR_R
#endif

#else // BOOST_WINDOWS_API

#include <cwchar>
#include <windows.h>

#include "windows_tools.hpp"

#endif  // BOOST_WINDOWS_API

#include "error_handling.hpp"

//  BOOST_FILESYSTEM_STATUS_CACHE enables file_status cache in
//  dir_itr_increment. The config tests are placed here because some of the
//  macros being tested come from dirent.h.
//
// TODO: find out what macros indicate dirent::d_type present in more libraries
#if defined(BOOST_WINDOWS_API)\
  || defined(_DIRENT_HAVE_D_TYPE)// defined by GNU C library if d_type present
#define BOOST_FILESYSTEM_STATUS_CACHE
#endif

namespace fs = boost::filesystem;
using boost::system::error_code;
using boost::system::system_category;

namespace boost {
namespace filesystem {

//--------------------------------------------------------------------------------------//
//                                                                                      //
//                                 directory_entry                                      //
//                                                                                      //
//--------------------------------------------------------------------------------------//

BOOST_FILESYSTEM_DECL
file_status directory_entry::get_status(system::error_code* ec) const
{
  if (!status_known(m_status))
  {
    // optimization: if the symlink status is known, and it isn't a symlink,
    // then status and symlink_status are identical so just copy the
    // symlink status to the regular status.
    if (status_known(m_symlink_status) && !is_symlink(m_symlink_status))
    {
      m_status = m_symlink_status;
      if (ec != 0)
        ec->clear();
    }
    else
    {
      m_status = detail::status(m_path, ec);
    }
  }
  else if (ec != 0)
  {
    ec->clear();
  }

  return m_status;
}

BOOST_FILESYSTEM_DECL
file_status directory_entry::get_symlink_status(system::error_code* ec) const
{
  if (!status_known(m_symlink_status))
    m_symlink_status = detail::symlink_status(m_path, ec);
  else if (ec != 0)
    ec->clear();

  return m_symlink_status;
}

//  dispatch directory_entry supplied here rather than in
//  <boost/filesystem/path_traits.hpp>, thus avoiding header circularity.
//  test cases are in operations_unit_test.cpp

namespace path_traits {

void dispatch(const directory_entry& de,
#ifdef BOOST_WINDOWS_API
  std::wstring& to,
#else
  std::string& to,
#endif
  const codecvt_type&)
{
  to = de.path().native();
}

void dispatch(const directory_entry& de,
#ifdef BOOST_WINDOWS_API
  std::wstring& to
#else
  std::string& to
#endif
  )
{
  to = de.path().native();
}

} // namespace path_traits

//--------------------------------------------------------------------------------------//
//                                                                                      //
//                               directory_iterator                                     //
//                                                                                      //
//--------------------------------------------------------------------------------------//

namespace detail {

namespace {

#ifdef BOOST_POSIX_API

#if defined(BOOST_FILESYSTEM_USE_READDIR_R)

// Obtains maximum length of a path, not including the terminating zero
inline std::size_t get_path_max()
{
  // this code is based on Stevens and Rago, Advanced Programming in the
  // UNIX envirnment, 2nd Ed., ISBN 0-201-43307-9, page 49
  std::size_t max = 0;
  errno = 0;
  long res = ::pathconf("/", _PC_PATH_MAX);
  if (res < 0)
  {
#if defined(PATH_MAX)
    max = PATH_MAX;
#else
    max = 4096;
#endif
  }
  else
  {
    max = static_cast< std::size_t >(res); // relative root
#if defined(PATH_MAX)
    if (max < PATH_MAX)
      max = PATH_MAX;
#endif
  }

  if ((max + 1) < sizeof(dirent().d_name))
    max = sizeof(dirent().d_name) - 1;

  return max;
}

// Returns maximum length of a path, not including the terminating zero
inline std::size_t path_max()
{
  static const std::size_t max = get_path_max();
  return max;
}

#endif // BOOST_FILESYSTEM_USE_READDIR_R

error_code dir_itr_first(void*& handle, void*& buffer,
  const char* dir, std::string& target,
  fs::file_status&, fs::file_status&)
{
  if ((handle = ::opendir(dir)) == 0)
  {
    const int err = errno;
    return error_code(err, system_category());
  }
  target.assign(".");  // string was static but caused trouble
                       // when iteration called from dtor, after
                       // static had already been destroyed
  return error_code();
}

// *result set to NULL on end of directory
inline int readdir_r_simulator(DIR* dirp, void*& buffer, struct dirent** result)
{
#if defined(BOOST_FILESYSTEM_USE_READDIR_R)
  errno = 0;

  if (::sysconf(_SC_THREAD_SAFE_FUNCTIONS) >= 0)
  {
    struct dirent* storage = static_cast< struct dirent* >(buffer);
    if (BOOST_UNLIKELY(!storage))
    {
      // According to readdir description, there's no reliable way to predict the length of the d_name string.
      // It may exceed NAME_MAX and pathconf(_PC_NAME_MAX) limits. We are being conservative here and allocate
      // buffer that is enough for PATH_MAX as the directory name. Still, this doesn't guarantee there won't be
      // a buffer overrun. The readdir_r API is fundamentally flawed and we should avoid it as much as possible
      // in favor of readdir.
      const std::size_t name_size = path_max();
      const std::size_t buffer_size = (sizeof(dirent) - sizeof(dirent().d_name)) + name_size + 1; // + 1 for "\0"
      buffer = storage = static_cast< struct dirent* >(std::malloc(buffer_size));
      if (BOOST_UNLIKELY(!storage))
        return boost::system::errc::not_enough_memory;
      std::memset(storage, 0, buffer_size);
    }

    return ::readdir_r(dirp, storage, result);
  }
#endif

  errno = 0;

  struct dirent* p = ::readdir(dirp);
  *result = p;
  if (!p)
    return errno;
  return 0;
}

error_code dir_itr_increment(void*& handle, void*& buffer,
  std::string& target, fs::file_status& sf, fs::file_status& symlink_sf)
{
  dirent* result = NULL;
  int err = readdir_r_simulator(static_cast<DIR*>(handle), buffer, &result);
  if (BOOST_UNLIKELY(err != 0))
    return error_code(err, system_category());
  if (result == NULL)
    return fs::detail::dir_itr_close(handle, buffer);

  target = result->d_name;

#ifdef BOOST_FILESYSTEM_STATUS_CACHE
  if (result->d_type == DT_UNKNOWN) // filesystem does not supply d_type value
  {
    sf = symlink_sf = fs::file_status(fs::status_error);
  }
  else  // filesystem supplies d_type value
  {
    if (result->d_type == DT_DIR)
      sf = symlink_sf = fs::file_status(fs::directory_file);
    else if (result->d_type == DT_REG)
      sf = symlink_sf = fs::file_status(fs::regular_file);
    else if (result->d_type == DT_LNK)
    {
      sf = fs::file_status(fs::status_error);
      symlink_sf = fs::file_status(fs::symlink_file);
    }
    else
      sf = symlink_sf = fs::file_status(fs::status_error);
  }
#else
  sf = symlink_sf = fs::file_status(fs::status_error);
#endif
  return error_code();
}

#else // BOOST_WINDOWS_API

error_code dir_itr_first(void*& handle, const fs::path& dir,
  std::wstring& target, fs::file_status& sf, fs::file_status& symlink_sf)
// Note: an empty root directory has no "." or ".." entries, so this
// causes a ERROR_FILE_NOT_FOUND error which we do not considered an
// error. It is treated as eof instead.
{
  // use a form of search Sebastian Martel reports will work with Win98
  std::wstring dirpath(dir.wstring());
  dirpath += (dirpath.empty()
    || (dirpath[dirpath.size()-1] != L'\\'
      && dirpath[dirpath.size()-1] != L'/'
      && dirpath[dirpath.size()-1] != L':'))? L"\\*" : L"*";

  WIN32_FIND_DATAW data;
  if ((handle = ::FindFirstFileW(dirpath.c_str(), &data))
    == INVALID_HANDLE_VALUE)
  {
    handle = 0;  // signal eof
    DWORD error = ::GetLastError();
    return error_code( (error == ERROR_FILE_NOT_FOUND
                     // Windows Mobile returns ERROR_NO_MORE_FILES; see ticket #3551
                     || error == ERROR_NO_MORE_FILES)
      ? 0 : error, system_category() );
  }
  target = data.cFileName;
  if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
  // reparse points are complex, so don't try to handle them here; instead just mark
  // them as status_error which causes directory_entry caching to call status()
  // and symlink_status() which do handle reparse points fully
  {
    sf.type(fs::status_error);
    symlink_sf.type(fs::status_error);
  }
  else
  {
    if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
    {
      sf.type(fs::directory_file);
      symlink_sf.type(fs::directory_file);
    }
    else
    {
      sf.type(fs::regular_file);
      symlink_sf.type(fs::regular_file);
    }
    sf.permissions(make_permissions(data.cFileName, data.dwFileAttributes));
    symlink_sf.permissions(sf.permissions());
  }
  return error_code();
}

error_code dir_itr_increment(void*& handle, std::wstring& target, fs::file_status& sf, fs::file_status& symlink_sf)
{
  WIN32_FIND_DATAW data;
  if (::FindNextFileW(handle, &data)== 0)// fails
  {
    DWORD error = ::GetLastError();
    fs::detail::dir_itr_close(handle);
    return error_code(error == ERROR_NO_MORE_FILES ? 0 : error, system_category());
  }
  target = data.cFileName;
  if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
  // reparse points are complex, so don't try to handle them here; instead just mark
  // them as status_error which causes directory_entry caching to call status()
  // and symlink_status() which do handle reparse points fully
  {
    sf.type(fs::status_error);
    symlink_sf.type(fs::status_error);
  }
  else
  {
    if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
    {
      sf.type(fs::directory_file);
      symlink_sf.type(fs::directory_file);
    }
    else
    {
      sf.type(fs::regular_file);
      symlink_sf.type(fs::regular_file);
    }
    sf.permissions(make_permissions(data.cFileName, data.dwFileAttributes));
    symlink_sf.permissions(sf.permissions());
  }
  return error_code();
}
#endif

BOOST_CONSTEXPR_OR_CONST err_t not_found_error_code =
#ifdef BOOST_WINDOWS_API
  ERROR_PATH_NOT_FOUND
#else
  ENOENT
#endif
;

} // namespace

//  dir_itr_close is called both from the ~dir_itr_imp()destructor
//  and dir_itr_increment()
BOOST_FILESYSTEM_DECL
system::error_code dir_itr_close( // never throws
  void*& handle
#if defined(BOOST_POSIX_API)
  , void*& buffer
#endif
  ) BOOST_NOEXCEPT
{
#ifdef BOOST_POSIX_API

  if (buffer != NULL)
  {
    std::free(buffer);
    buffer = NULL;
  }

  if (handle != NULL)
  {
    DIR* h = static_cast<DIR*>(handle);
    handle = NULL;
    int err = 0;
    if (BOOST_UNLIKELY(::closedir(h) != 0))
    {
      err = errno;
      return error_code(err, system_category());
    }
  }

  return error_code();

#else

  if (handle != NULL)
  {
    ::FindClose(handle);
    handle = NULL;
  }
  return error_code();

#endif
}

BOOST_FILESYSTEM_DECL
void directory_iterator_construct(directory_iterator& it, const path& p, unsigned int opts, system::error_code* ec)
{
  if (error(p.empty() ? not_found_error_code : 0, p, ec,
            "boost::filesystem::directory_iterator::construct"))
  {
    return;
  }

  boost::intrusive_ptr< detail::dir_itr_imp > imp;
  if (!ec)
  {
    imp = new detail::dir_itr_imp();
  }
  else
  {
    imp = new (std::nothrow) detail::dir_itr_imp();
    if (BOOST_UNLIKELY(!imp))
    {
      *ec = make_error_code(system::errc::not_enough_memory);
      return;
    }
  }

  try
  {
    path::string_type filename;
    file_status file_stat, symlink_file_stat;
    error_code result = dir_itr_first(imp->handle,
#     if defined(BOOST_POSIX_API)
      imp->buffer,
#     endif
      p.c_str(), filename, file_stat, symlink_file_stat);

    if (result)
    {
      if (result != make_error_condition(system::errc::permission_denied) ||
        (opts & static_cast< unsigned int >(directory_options::skip_permission_denied)) == 0u)
      {
        error(result.value(), p,
          ec, "boost::filesystem::directory_iterator::construct");
      }

      return;
    }

    if (imp->handle)
    {
      // Not eof
      it.m_imp.swap(imp);
      it.m_imp->dir_entry.assign(p / filename, file_stat, symlink_file_stat);
      const path::string_type::value_type* filename_str = filename.c_str();
      if (filename_str[0] == path::dot // dot or dot-dot
        && (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'))))
      {
        detail::directory_iterator_increment(it, ec);
      }
    }
  }
  catch (std::bad_alloc&)
  {
    if (!ec)
      throw;

    *ec = make_error_code(boost::system::errc::not_enough_memory);
    it.m_imp.reset();
  }
}

BOOST_FILESYSTEM_DECL
void directory_iterator_increment(directory_iterator& it, system::error_code* ec)
{
  BOOST_ASSERT_MSG(!it.is_end(), "attempt to increment end iterator");

  if (ec)
    ec->clear();

  try
  {
    path::string_type filename;
    file_status file_stat, symlink_file_stat;
    system::error_code increment_ec;

    for (;;)
    {
      increment_ec = dir_itr_increment(it.m_imp->handle,
#       if defined(BOOST_POSIX_API)
        it.m_imp->buffer,
#       endif
        filename, file_stat, symlink_file_stat);

      if (BOOST_UNLIKELY(!!increment_ec))  // happens if filesystem is corrupt, such as on a damaged optical disc
      {
        boost::intrusive_ptr< detail::dir_itr_imp > imp;
        imp.swap(it.m_imp);
        path error_path(imp->dir_entry.path().parent_path());  // fix ticket #5900
        if (ec == NULL)
        {
          BOOST_FILESYSTEM_THROW(
            filesystem_error("boost::filesystem::directory_iterator::operator++",
              error_path,
              increment_ec));
        }
        *ec = increment_ec;
        return;
      }

      if (it.m_imp->handle == NULL)  // eof, make end
      {
        it.m_imp.reset();
        return;
      }

      const path::string_type::value_type* filename_str = filename.c_str();
      if (!(filename_str[0] == path::dot // !(dot or dot-dot)
        && (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')))))
      {
        it.m_imp->dir_entry.replace_filename(filename, file_stat, symlink_file_stat);
        return;
      }
    }
  }
  catch (std::bad_alloc&)
  {
    if (!ec)
      throw;

    it.m_imp.reset();
    *ec = make_error_code(boost::system::errc::not_enough_memory);
  }
}

//--------------------------------------------------------------------------------------//
//                                                                                      //
//                           recursive_directory_iterator                               //
//                                                                                      //
//--------------------------------------------------------------------------------------//

BOOST_FILESYSTEM_DECL
void recursive_directory_iterator_construct(recursive_directory_iterator& it, const path& dir_path, unsigned int opts, system::error_code* ec)
{
  if (ec)
    ec->clear();

  directory_iterator dir_it;
  detail::directory_iterator_construct(dir_it, dir_path, opts, ec);
  if ((ec && *ec) || dir_it == directory_iterator())
    return;

  boost::intrusive_ptr< detail::recur_dir_itr_imp > imp;
  if (!ec)
  {
    imp = new detail::recur_dir_itr_imp(opts);
  }
  else
  {
    imp = new (std::nothrow) detail::recur_dir_itr_imp(opts);
    if (BOOST_UNLIKELY(!imp))
    {
      *ec = make_error_code(system::errc::not_enough_memory);
      return;
    }
  }

  try
  {
#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES)
    imp->m_stack.push_back(std::move(dir_it));
#else
    imp->m_stack.push_back(dir_it);
#endif

    it.m_imp.swap(imp);
  }
  catch (std::bad_alloc&)
  {
    if (ec)
    {
      *ec = make_error_code(system::errc::not_enough_memory);
      return;
    }

    throw;
  }
}

namespace {

void recursive_directory_iterator_pop_on_error(detail::recur_dir_itr_imp* imp)
{
  imp->m_stack.pop_back();

  while (!imp->m_stack.empty())
  {
    directory_iterator& dir_it = imp->m_stack.back();
    system::error_code increment_ec;
    detail::directory_iterator_increment(dir_it, &increment_ec);
    if (!increment_ec && dir_it != directory_iterator())
      break;

    imp->m_stack.pop_back();
  }
}

} // namespace

BOOST_FILESYSTEM_DECL
void recursive_directory_iterator_pop(recursive_directory_iterator& it, system::error_code* ec)
{
  BOOST_ASSERT_MSG(!it.is_end(), "pop() on end recursive_directory_iterator");
  detail::recur_dir_itr_imp* const imp = it.m_imp.get();

  if (ec)
    ec->clear();

  imp->m_stack.pop_back();

  while (true)
  {
    if (imp->m_stack.empty())
    {
      it.m_imp.reset(); // done, so make end iterator
      break;
    }

    directory_iterator& dir_it = imp->m_stack.back();
    system::error_code increment_ec;
    detail::directory_iterator_increment(dir_it, &increment_ec);
    if (BOOST_UNLIKELY(!!increment_ec))
    {
      if ((imp->m_options & static_cast< unsigned int >(directory_options::pop_on_error)) == 0u)
      {
        // Make an end iterator on errors
        it.m_imp.reset();
      }
      else
      {
        recursive_directory_iterator_pop_on_error(imp);

        if (imp->m_stack.empty())
          it.m_imp.reset(); // done, so make end iterator
      }

      if (ec == NULL)
      {
        BOOST_FILESYSTEM_THROW(
          filesystem_error("boost::filesystem::recursive_directory_iterator::pop", increment_ec));
      }

      *ec = increment_ec;
      return;
    }

    if (dir_it != directory_iterator())
      break;

    imp->m_stack.pop_back();
  }
}

namespace {

enum push_directory_result
{
  directory_not_pushed = 0u,
  directory_pushed = 1u,
  keep_depth = 1u << 1
};

// Returns: true if push occurs, otherwise false. Always returns false on error.
inline push_directory_result recursive_directory_iterator_push_directory(detail::recur_dir_itr_imp* imp, system::error_code& ec) BOOST_NOEXCEPT
{
  push_directory_result result = directory_not_pushed;
  try
  {
    //  Discover if the iterator is for a directory that needs to be recursed into,
    //  taking symlinks and options into account.

    if ((imp->m_options & static_cast< unsigned int >(directory_options::_detail_no_push)) != 0u)
    {
      imp->m_options &= ~static_cast< unsigned int >(directory_options::_detail_no_push);
      return result;
    }

    file_status symlink_stat;

    // if we are not recursing into symlinks, we are going to have to know if the
    // stack top is a symlink, so get symlink_status and verify no error occurred
    if ((imp->m_options & static_cast< unsigned int >(directory_options::follow_directory_symlink)) == 0u ||
      (imp->m_options & static_cast< unsigned int >(directory_options::skip_dangling_symlinks)) != 0u)
    {
      symlink_stat = imp->m_stack.back()->symlink_status(ec);
      if (ec)
        return result;
    }

    // Logic for following predicate was contributed by Daniel Aarno to handle cyclic
    // symlinks correctly and efficiently, fixing ticket #5652.
    //   if (((m_options & directory_options::follow_directory_symlink) == directory_options::follow_directory_symlink
    //         || !is_symlink(m_stack.back()->symlink_status()))
    //       && is_directory(m_stack.back()->status())) ...
    // The predicate code has since been rewritten to pass error_code arguments,
    // per ticket #5653.

    if ((imp->m_options & static_cast< unsigned int >(directory_options::follow_directory_symlink)) != 0u || !fs::is_symlink(symlink_stat))
    {
      file_status stat = imp->m_stack.back()->status(ec);
      if (BOOST_UNLIKELY(!!ec))
      {
        if (ec == make_error_condition(system::errc::no_such_file_or_directory) && fs::is_symlink(symlink_stat) &&
          (imp->m_options & static_cast< unsigned int >(directory_options::follow_directory_symlink | directory_options::skip_dangling_symlinks))
          == static_cast< unsigned int >(directory_options::follow_directory_symlink | directory_options::skip_dangling_symlinks))
        {
          // Skip dangling symlink and continue iteration on the current depth level
          ec = error_code();
        }

        return result;
      }

      if (!fs::is_directory(stat))
        return result;

      if (BOOST_UNLIKELY((imp->m_stack.size() - 1u) >= static_cast< std::size_t >((std::numeric_limits< int >::max)())))
      {
        // We cannot let depth to overflow
        ec = make_error_code(system::errc::value_too_large);
        // When depth overflow happens, avoid popping the current directory iterator
        // and attempt to continue iteration on the current depth.
        result = keep_depth;
        return result;
      }

      directory_iterator next(imp->m_stack.back()->path(), static_cast< BOOST_SCOPED_ENUM_NATIVE(directory_options) >(imp->m_options), ec);
      if (!ec && next != directory_iterator())
      {
#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES)
        imp->m_stack.push_back(std::move(next)); // may throw
#else
        imp->m_stack.push_back(next); // may throw
#endif
        return directory_pushed;
      }
    }
  }
  catch (std::bad_alloc&)
  {
    ec = make_error_code(system::errc::not_enough_memory);
  }

  return result;
}

} // namespace

BOOST_FILESYSTEM_DECL
void recursive_directory_iterator_increment(recursive_directory_iterator& it, system::error_code* ec)
{
  BOOST_ASSERT_MSG(!it.is_end(), "increment() on end recursive_directory_iterator");
  detail::recur_dir_itr_imp* const imp = it.m_imp.get();

  if (ec)
    ec->clear();

  system::error_code local_ec;

  //  if various conditions are met, push a directory_iterator into the iterator stack
  push_directory_result push_result = recursive_directory_iterator_push_directory(imp, local_ec);
  if (push_result == directory_pushed)
    return;

  // report errors if any
  if (BOOST_UNLIKELY(!!local_ec))
  {
  on_error:
    if ((imp->m_options & static_cast< unsigned int >(directory_options::pop_on_error)) == 0u)
    {
      // Make an end iterator on errors
      it.m_imp.reset();
    }
    else
    {
      if ((push_result & keep_depth) != 0u)
      {
        system::error_code increment_ec;
        directory_iterator& dir_it = imp->m_stack.back();
        detail::directory_iterator_increment(dir_it, &increment_ec);
        if (!increment_ec && dir_it != directory_iterator())
          goto on_error_return;
      }

      recursive_directory_iterator_pop_on_error(imp);

      if (imp->m_stack.empty())
        it.m_imp.reset(); // done, so make end iterator
    }

  on_error_return:
    if (ec == NULL)
    {
      BOOST_FILESYSTEM_THROW(filesystem_error(
        "filesystem::recursive_directory_iterator increment error",
        local_ec));
    }

    *ec = local_ec;
    return;
  }

  //  Do the actual increment operation on the top iterator in the iterator
  //  stack, popping the stack if necessary, until either the stack is empty or a
  //  non-end iterator is reached.
  while (true)
  {
    if (imp->m_stack.empty())
    {
      it.m_imp.reset(); // done, so make end iterator
      break;
    }

    directory_iterator& dir_it = imp->m_stack.back();
    detail::directory_iterator_increment(dir_it, &local_ec);
    if (BOOST_UNLIKELY(!!local_ec))
      goto on_error;

    if (dir_it != directory_iterator())
      break;

    imp->m_stack.pop_back();
  }
}

} // namespace detail

} // namespace filesystem
} // namespace boost
