1

I have the following program that iterates through a directory and can catch exceptions:

#include <iostream>
#include <filesystem>
#include <exception>

int main() {

    const std::filesystem::path md("/");

    try {

        for(auto const& dir : std::filesystem::recursive_directory_iterator(md)) {
            std::cout << dir.path() << std::endl;
        }

    }
    catch(const std::exception& e) {
        std::cout << "Error: " << e.what() << std::endl;
    }

    return 0;
}

The problem is, whenever an exception is thrown it exits the for loop and end the program. Is there any way to go back to the position where the for loop left off after handling the exception?

EDIT: The error is caused by the for loop recursive_directory_iterator not from inside the loop. It is guaranteed to throw the same error at the same position in the loop every time so I need continue to the loop at the position after the error is thrown.

  • I don't think, there's a recommendable way. – πάντα ῥεῖ Feb 11 '22 at 20:11
  • 1
    Yes. Add a try/catch block inside the loop. The catch can decide whether to continue or re-throw. – Drew Dormann Feb 11 '22 at 20:12
  • @Drew Dormann The error is being thrown by the for loop itself when it calls recursive_directory_iterator. How do I put a try catch block there? – Gary Fisher Feb 11 '22 at 20:19
  • @GaryFisher in that case - where the loop never began - you just need a loop outside of the try/catch you already have. Keep attempting the inner loop until it satisfies whatever condition you want. – Drew Dormann Feb 11 '22 at 20:26
  • 1
    I suggest rewriting your code to recursively use the std::filesystem::directory_iterator . This will give you more control over which directory is causing the problem. – RainMoose Feb 11 '22 at 20:33
  • @DrewDormann The same error is guaranteed to happen at the same point every time. I need to catch that error and then continue to the _next_ point in the range. If I just encase the whole thing in the loop it just keeps giving the same error on and on forever. – Gary Fisher Feb 11 '22 at 20:53
  • You may want to [edit] your question to include these comment details. I realize that I am making guesses about the problem described here, and I am not guessing well. I'm still not entirely sure whether you are handling an exception at the loop's initialization (calling `recursive_directory_iterator(md)`, once) or within the loop itself. – Drew Dormann Feb 11 '22 at 20:56
  • 1
    @GaryFisher What is the actual error that is being thrown? If it is just a permission error, you can use the `skip_permission_denied` flag when constructing the iterator. – Remy Lebeau Feb 11 '22 at 21:39
  • @RemyLebeau The error I am getting is `filesystem error: in recursive_directory_iterator::operator++(): attempting recursion into "/usr/sbin/authserver": Permission denied`, so skip permission denied fixes it, _however_, after doing that I get a new error: `filesystem error: in recursive_directory_iterator::operator++(): attempting recursion into "/usr/sbin/authserver": Permission denied` – Gary Fisher Feb 11 '22 at 21:58
  • @GaryFisher umm, that is the same error... – Remy Lebeau Feb 11 '22 at 22:02
  • @RemyLebeau oh sorry I copied the wrong one the second error is actually `filesystem error: in recursive_directory_iterator::operator++(): attempting recursion into "/Library/Application Support/com.apple.TCC": Operation not permitted` – Gary Fisher Feb 11 '22 at 22:12
  • Oh yeah, good old Apple. They have two 'flavours' of 'permission denied', and it looks like `skip_permission_denied ` doesn't cater for the second one. Bummer. – Paul Sanders Feb 11 '22 at 23:08
  • @PaulSanders Is there anything like `skip_permission_denied` that will work for that exception? – Gary Fisher Feb 12 '22 at 01:00
  • Not that I know of, no. It's a different underlying error (i.e a different value returned in `errno`) which the author of `std::filesystem` obviously forgot to handle. Is your program sandboxed? `Operation not permitted` is much more common in sandboxed apps, although judging by what you say it isn't. Of course, nobody has asked you why you're trawling through _every_ directory on the machine (including secondary volumes and networked drives). That's not a common thing to do (and will probably take a while). – Paul Sanders Feb 12 '22 at 16:46
  • @PaulSanders It is not sandboxed and the reason I am looking through every file is to search for a file inside of a specific directory, the directory is just currently set to the whole file system – Gary Fisher Feb 13 '22 at 00:35
  • Oh, OK. Do you still get the second problem if you search the _actual_ directory you want to search? The one you cite in your comment is evidently off-limits, best to avoid it if you can. – Paul Sanders Feb 13 '22 at 12:22
  • Ok that works, I still get the Permission Denied error(which can be fixed with skip permission denied), but there is no Operation not permitted anymore. :) – Gary Fisher Feb 14 '22 at 15:35

1 Answers1

2

What you want is not fully possible with std::filesystem::recursive_directory_iterator, even if you write the loop manually.

There are two places where the iterator can throw exceptions1, the de-referencing operator (*it) and the increment operator (++it).

If any of these reports an error (i.e., throws an exception in your case), the iterator cannot be used anymore.2

For the de-referencing iterator, you can backup the iterator and skip invalid entries

auto it = std::filesystem::recursive_directory_iterator{md};
while (it != std::filesystem::recursive_directory_iterator{}) {
    const auto tmp_it = it;
    try {
        const auto& path = *tmp_it;
        // use path...
    }
    catch (std::filesystem::filesystem_error const&) {

    }

    // you can still increment it here
    ++it;
}

Unfortunately, if the increment operator reports an error (throws or reports an std::error_code), there is not much you can do to simply "skip" that element — you can handle some cases, but not everything, e.g.

std::error_code ec;
const auto backup = it;

// try increment
it.increment(ec);

// failed
if (ec) {
    it = backup;

    // the iterator tried to enter a directory and failed, disable the 
    // recursion for the next entry
    if (it.recursion_pending()) {
        it.disable_recursion_pending();

        // try again
        it.increment(ec);
    }
}

// still failed, or was not recursion_pending()
if (ec) {
    it = backup;

    // try to skip the remaining entry at the given depth go back to 
    // the parent      
    it.pop(ec);
}

// nothing much you can do here unfortunately
if (ec) {

}

1 The constructor can also report error but in this case you do not even have a valid iterator to begin with so that is not relevant for your question.

2 Standard quotes:

[filesystems#fs.class.directory.iterator.general-3]

If an iterator of type directory_­iterator reports an error or is advanced past the last directory element, that iterator shall become equal to the end iterator value. [..]

[filesystems#fs.class.rec.dir.itr.general-3]

The behavior of a recursive_­directory_­iterator is the same as a directory_­iterator unless otherwise specified.

Holt
  • 36,600
  • 7
  • 92
  • 139