问题
I am trying to work around an exception being thrown when trying to recursively walk through all files in root drives, like C:, D:, etc. I am using GCC compiler version 9.3.0 on Mingw64.
I got std::filesystem::filesystem_error when trying to read system volume information, example output:
Checking "D:\\System Volume Information"
filesystem error: cannot increment recursive directory iterator: Invalid argument
Code snippet:
try {
for (auto& p : fs::recursive_directory_iterator(dp, fs::directory_options::skip_permission_denied)) {
cout << "Checking " << p.path() << endl;
string path = p.path().string();
if (fs::is_regular_file(p) && p.path().extension() == ".xyz") {
files.push_back(p.path().string());
}
}
}
catch (fs::filesystem_error &e) {
// How to go back, skip this, and resume?
cerr << e.what() << endl;
}
What I would like to do is skip over these exceptions. Does anyone know how to do that?
Thank you!
回答1:
Since your error refers to incrementing the recursive_filesystem_iterator
, the error appears to be coming from the for
statement itself, not your subsequent code. The for
statement is internally doing an increment (operator++
) on the recursive_filesystem_iterator
.
To me, this feels like an error in the implementation of recursive_filesystem_iterator
, and your code should have worked with no exceptions. But reading the standard closely, I suppose there are enough ambiguities for an implementation to say that the behavior you see still conforms to the standard.
I don't have an official copy of the c++17 standard, so the references I give here are to the freely-available draft n4659.pdf.
At 30.10.2.1 Posix conformance
, it says
Implementations that do not support exact POSIX behavior are encouraged to provide
behavior as close to POSIX behavior as is reasonable given the limitations of actual
operating systems and file systems. If an implementation cannot provide any reasonable
behavior, the implementation shall report an error as specified in 30.10.7. [Note:This
allows users to rely on an exception being thrown or an error code being set when an
implementation cannot provide any reasonable behavior.— end note]
Implementations are not required to provide behavior that is not supported by a
particular file system. [Example: The FAT file system used by some memory cards, camera
memory, and floppy disks does not support hard links, symlinks, and many other features
of more capable file systems, so implementations are not required to support those
features on the FAT file system but instead are required to report an error as described
above.— end example]
So an attempt to iterate into D:\System Volume Information
may fail and throw an exception if the underlying filesystem won't let you do that.
Your constructor specifies directory_options::skip_permission_denied
. I seems like this should be sufficient to avoid the exception.
In 30.10.14.1 recursive_directory_iterator members
for operator++
it says:
...then either directory(*this)->path() is recursively iterated into or, if
(options() & directory_options::skip_permission_denied) != directory_options::none
and an error occurs indicating that permission to access directory(*this)->path() is denied,
then directory(*this)->path() is treated as an empty directory and no error is reported.
The actual exception that you get doesn't say "permission denied", so I guess it could be argued that the skip_permission_denied
option does not apply to it. This would allow the implementation of operator++
to throw an exception in this case. I don't like this interpretation, since it seems like the whole idea of skip_permission_denied
is to avoid exceptions like this. But it's not up to me. :)
Aside from perhaps trying to submit a defect back to your standard library implementation, what can you do? Perhaps you could write out an old-style for
loop, and use the increment
method on the recursive_filesystem_iterator
. The increment
method returns an error code rather than throwing an exception. So your code would look something like:
auto iter = fs::recursive_directory_iterator(dp, fs::directory_options::skip_permission_denied);
auto end_iter = fs::end(iter);
auto ec = std::error_code();
for (; iter != end_iter; iter.increment(ec))
{
if (ec)
{
continue;
}
// The rest of your loop code here...
}
I think the above looks reasonable, but definitely needs testing to make sure there's not some weird corner case where you get an infinite loop or something. Actually I'm not so sure the continue
stuff is even needed, but you may want to experiment with it.
Finally, when you catch a filesystem_error
, you could print out e.path1.native()
in addition to e.what()
. I think you already mostly know that information because you're printing the path within your loop. But it might provide more information in some cases.
来源:https://stackoverflow.com/questions/62988629/c-stdfilesystemfilesystem-error-exception-trying-to-read-system-volume-inf