Skip to content

recursive_directory_iterator::increment does not advance on error #112

@xRodney

Description

@xRodney

I'm using the latest develop branch (will be 1.71)

From operations.cpp:

  // Invariant: On return, the top of the iterator stack is the next valid (possibly
  // end) iterator, regardless of whether or not an error is reported, and regardless of
  // whether any error is reported by exception or error code. In other words, progress
  // is always made so a loop on the iterator will always eventually terminate
  // regardless of errors.
  BOOST_FILESYSTEM_DECL
  void recur_dir_itr_imp::increment(system::error_code* ec)

This is not (always?) true as the iterator does not advance when an error is reported during push_directory.

Steps to reproduce

Consider this program (slightly modified simple_ls.cpp from examples)

recursive_ls.cpp

#define BOOST_FILESYSTEM_VERSION 3

//  As an example program, we don't want to use any deprecated features
#ifndef BOOST_FILESYSTEM_NO_DEPRECATED 
#  define BOOST_FILESYSTEM_NO_DEPRECATED
#endif
#ifndef BOOST_SYSTEM_NO_DEPRECATED 
#  define BOOST_SYSTEM_NO_DEPRECATED
#endif

#include "boost/filesystem/operations.hpp"
#include "boost/filesystem/path.hpp"
#include "boost/progress.hpp"
#include <iostream>

namespace fs = boost::filesystem;

int main(int argc, char* argv[])
{
  fs::path p(fs::current_path());

  if (argc > 1)
    p = fs::system_complete(argv[1]);
  else
    std::cout << "\nusage:   recursive_ls [path]" << std::endl;

  unsigned long file_count = 0;
  unsigned long dir_count = 0;
  unsigned long other_count = 0;
  unsigned long err_count = 0;

  if (!fs::exists(p))
  {
    std::cout << "\nNot found: " << p << std::endl;
    return 1;
  }

  if (fs::is_directory(p))
  {
    std::cout << "\nIn directory: " << p << "\n\n";
    fs::recursive_directory_iterator end_iter;
    for (fs::recursive_directory_iterator dir_itr(p);
          dir_itr != end_iter;)
    {
      try
      {
        if (fs::is_directory(dir_itr->status()))
        {
          ++dir_count;
          std::cout << dir_itr->path() << " [directory]\n";
        }
        else if (fs::is_regular_file(dir_itr->status()))
        {
          ++file_count;
          std::cout << dir_itr->path() << "\n";
        }
        else
        {
          ++other_count;
          std::cout << dir_itr->path() << " [other]\n";
        }

        boost::system::error_code ec;
        dir_itr.increment(ec);
        if (ec)
        {
            ++err_count;
            std::cout << "*" << dir_itr->path() << " *" << ec.message() << std::endl;
        }
      }
      catch (const std::exception & ex)
      {
        ++err_count;
        std::cout << dir_itr->path() << " " << ex.what() << std::endl;
      }
    }
    std::cout << "\n" << file_count << " files\n"
              << dir_count << " directories\n"
              << other_count << " others\n"
              << err_count << " errors\n";
  }
  else // must be a file
  {
    std::cout << "\nFound: " << p << "\n";    
  }
  return 0;
}

Now run the following commands (tested on linux):

mkdir testdir
chmod 000 testdir
mkdir otherdir
./recursive_ls

Actual result

usage:   recursive_ls [path]

In directory: "/home/rodney/Projects/boost/boost/libs/filesystem/test/recursive_test"

"/home/rodney/Projects/boost/boost/libs/filesystem/test/recursive_test/testdir" [directory]
*"/home/rodney/Projects/boost/boost/libs/filesystem/test/recursive_test/testdir" *Permission denied
"/home/rodney/Projects/boost/boost/libs/filesystem/test/recursive_test/testdir" [directory]
*"/home/rodney/Projects/boost/boost/libs/filesystem/test/recursive_test/testdir" *Permission denied
"/home/rodney/Projects/boost/boost/libs/filesystem/test/recursive_test/testdir" [directory]
*"/home/rodney/Projects/boost/boost/libs/filesystem/test/recursive_test/testdir" *Permission denied
"/home/rodney/Projects/boost/boost/libs/filesystem/test/recursive_test/testdir" [directory]
*"/home/rodney/Projects/boost/boost/libs/filesystem/test/recursive_test/testdir" *Permission denied
"/home/rodney/Projects/boost/boost/libs/filesystem/test/recursive_test/testdir" [directory]
*"/home/rodney/Projects/boost/boost/libs/filesystem/test/recursive_test/testdir" *Permission denied

... infinite loop

Expected result

One of:

  1. Fullfill the invariant and make the iterator always advance. I can see that other tests expect that the for loop finishes without special handling or errors
  2. Drop the invariant and write into documentation that on errors, it.no_push() must be called in order to continue.

I'm not sure what is better.

The first would have the disadvantage that because the iterator has already advances, the current path would not correspond to the error code, like this:

"/home/rodney/Projects/boost/boost/libs/filesystem/test/recursive_test/testdir" [directory]
*"/home/rodney/Projects/boost/boost/libs/filesystem/test/recursive_test/otherdir" *Permission denied

This is because testdir record is read and reported just fine, the error only happens once we try to recurse into that directory. So the contents are skipped and iterator now points to the next record in the parent directory.

The second variant, on the other hand, only works for errors connected to directory opens. What about other errors? Should another api be added to "acknowledge" the current error?

Many thanks for a wonderful library.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions