recursive_directory_iterator's skip_permission_denied option appears to be ignored on macOS? - c++

Using C++20 and std::filesystem::recursive_directory_iterator on macOS, this code:
for (auto& f : recursive_directory_iterator(getenv("HOME"), directory_options::skip_permission_denied)) {
// dummy
}
Which should, according to my understanding of the documentation, skip directories which it does not have permission to recurse into, encounters an error upon trying to recurse into ~/Library/Application Support/MobileSync/.
However:
in recursive_directory_iterator::operator++(): attempting recursion into "/Users/t/Library/Application Support/MobileSync": Operation not permitted
I assume this means that there is some permission / security feature in place that the iterator will not skip over even if skip_permission_denied is present - what might this be, and how would I cleanly make the iterator skip over directories that cause it to break regardless of permissions?
I could manually disable_recursion_pending() when encountering known directories like MobileSync or .Trash that cause this problem, but that would be a messy solution compared to being able to detect in advance when a directory will cause this issue.

I'm afraid there is no easy way around it, as the iterator is "closed" on error so a post-error disable_recursion_pending will not help. I opened an issue for libcxx (https://github.com/llvm/llvm-project/issues/48870) and libstdc++ (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99533) that got worked on, but the libcxx one was not fixed yet, and even then it would have to make it into macOS as on macOS the standard library is part of the system.
One admittedly ugly but possible non-hardcoded workaround would be to blacklist dynamically and retry e.g. somewhere along the lines of:
// ugly workaround to iterate the home dir on macOS with std::filesystem:
std::set<fs::path> blacklist;
while(true) {
fs::path lastPath;
try {
for(auto rdi = fs::recursive_directory_iterator(getenv("HOME"), fs::directory_options::skip_permission_denied);
rdi != fs::recursive_directory_iterator();
++rdi) {
auto& de = *rdi;
lastPath = de.path();
if(blacklist.count(de.path())) {
rdi.disable_recursion_pending();
}
else {
// collect info you need here
// ...
}
++rdi;
}
}
catch (fs::filesystem_error& fe) {
if(!blacklist.insert(lastPath).second) {
// exception on same path, double error, something went really wrong
break;
}
else {
// we blacklisted a new entry, reset your collected info here,
// we need to restart
// ...
continue;
}
}
// we are through and managed to get all info
// ...
break;
}
Of course this is a lot of code working around something that should be a single line and only needed on macOS, so if one uses this at all, it should be wrapped away.
One subtle thing to be aware of is that a range-based-for uses the fs::begin(fs::recursive_directory_iterator) function that creates an "invisible" copy, and makes it impossible to call disable_recursion_pending() on the correct instance. This is the reason why a regular for-loop is used.
Another ugly part of that workaround is that besides the standards suggestions neither path1() nor path2() of the exception deliver the offending path and as parsing the exception text is a bad idea, the paths are remembered in the otherwise useless lastPath variable.
All in all it works, but this is nothing I would actually use, as the tree needs to be scanned multiple times (on my notebooks "Application Support" it takes six rescans until it gets through and four times the runtime of an implementation that works without this hack), so I would see this more as an experiment if it is possible to generically iterate the home on macOS with std::filesystem.
So, sadly, until those issues are fixed, std::filesystem::recursive_directory_iterator is not that great on macOS, and I continue to use my drop-in filesystem replacement on that platform, that honors skip_permission_denied for EPERM and EACCES (but is utf-8 only).

Related

Error with std::filesystem::copy copying a file to another pre-existing directory

See below for the following code, and below that, the error that follows.
std::string source = "C:\\Users\\cambarchian\\Documents\\tested";
std::string destination = "C:\\Users\\cambarchian\\Documents\\tester";
std::filesystem::path sourcepath = source;
std::filesystem::path destpath = destination;
std::filesystem::copy_options::update_existing;
std::filesystem::copy(sourcepath, destpath);
terminate called after throwing an instance of 'std::filesystem::__cxx11::filesystem_error'
what(): filesystem error: cannot copy: File exists [C:\Users\cambarchian\Documents\tested] [C:\Users\cambarchian\Documents\tester]
Tried to use filesystem::copy, along with trying different paths. No luck with anything. Not too much I can write here as the problem is listed above, could be a simple formatting issue. That being said, it worked on my home computer using visual studio 2022, however using VS Code with gcc 11.2 gives me this issue.
Using:
filesystem::copy_file(oldPath, newPath, filesystem::copy_options::overwrite_existing);
The overloads of std::filesystem::copy are documented. You're using the first overload, but want the second:
void copy(from, to) which is equivalent to [overload 2, below] using copy_options::none
void copy(from, to, options)
Writing the statement std::filesystem::copy_options::update_existing; before calling copy doesn't achieve anything at all, whereas passing the option to the correct overload like
std::filesystem::copy(sourcepath, destpath,
std::filesystem::copy_options::update_existing);
should do what you want.
... it worked on my home computer using visual studio 2022 ...
you don't say whether the destination file existed in that case, which is the first thing you should check.
I put the copy_options within the copy function but it didn't work so I started moving it around, I probably should have mentioned that.
Randomly permuting your code isn't a good way of generating clean examples for others to help with.
In the rare event that hacking away at something does fix it, I strongly recommend pausing to figure out why. When you've hacked away at something and it still doesn't work, by all means leave comments to remind yourself what you tried, but the code itself should still be in a good state.
Still doesn't work when I write std::filesystem::copy(sourcepath, destpath, std::filesystem::copy_options::recursive)
Well, that's a different option, isn't it? Were you randomly permuting which copy_options you selected as well?
Trying recursive and update_existing yields the same issue.
The documentation says
The behavior is undefined if there is more than one option in any of the copy_options option group present in options (even in the copy_file group).
so you shouldn't be setting both anyway. There's no benefit to recursively copying a single file, but there may be a benefit to updating or overwriting one. If the destination already exists. Which, according to your error, it does.
Since you do have an error explicitly saying "File exists", you should certainly look at the "options controlling copy_file() when the file already exists" section of the table here.
Visual Studio 2022 fixed the problem

Fastest way to make console output "verbose" or not

I am making a small system and I want to be able to toggle "verbose" text output in the whole system.
I have made a file called globals.h:
namespace REBr{
extern bool console_verbose = false;
}
If this is true I want all my classes to print a message to the console when they are constructing, destructing, copying or doing pretty much anything.
For example:
window(string title="",int width=1280,int height=720):
Width(width),Height(height),title(title)
{
if(console_verbose){
std::cout<<"Generating window #"<<this->instanceCounter;
std::cout<<"-";
}
this->window=SDL_CreateWindow(title.c_str(),0,0,width,height,SDL_WINDOW_OPENGL);
if(console_verbose)
std::cout<<"-";
if(this->window)
{
this->glcontext = SDL_GL_CreateContext(window);
if(console_verbose)
std::cout<<".";
if(this->glcontext==NULL)
{
std::cout<<"FATAL ERROR IN REBr::WINDOW::CONSTR_OPENGLCONTEXT: "<<SDL_GetError()<<std::endl;
}
}
else std::cout<<"FATAL ERROR IN REBr::WINDOW::CONSTR_WINDOW: "<<SDL_GetError()<<std::endl;
if(console_verbose)
std::cout<<">done!"<<endl;
}
Now as you can see I have a lot of ifs in that constructor. And I REALLY dont want that since that will slow down my application. I need this to be as fast as possible without removing the "loading bar" (this helps me determine at which function the program stopped functioning).
What is the best/fastest way to accomplish this?
Everying in my system is under the namespace REBr
Some variants to achieve that:
Use some logger library. It is the best option as it gives you maximum flexibility and some useful experience ;) And you haven't to devise something. For example, look at Google GLOG.
Define some macro, allowing you to turn on/off all these logs by changing only the macro. But it isn't so easy to write such marco correctly.
Mark your conditional flag as constexpr. That way you may switch the flag and, depending on its value, compiler will optimise ifs in compiled program. But ifs will still be in code, so it looks kinda bulky.
Anyway, all these options require program recompilation. W/o recompilation it is impossible to achieve the maximum speed.
I often use a Logger class that supports debug levels. A call might look like:
logger->Log(debugLevel, "%s %s %d %d", timestamp, msg, value1, value2);
The Logger class supports multiple debug levels so that I can fine tune the debug output. This can be set at any time through the command line or with a debugger. The Log statement uses a variable length argument list much like printf.
Google's logging module is widely used in the industry and supports logging levels that you can set from the command line. For example (taken from their documentation)
VLOG(1) << "I'm printed when you run the program with --v=1 or higher";
VLOG(2) << "I'm printed when you run the program with --v=2 or higher";
You can find the code here https://github.com/google/glog and the documentation in the doc/ folder.

VS2015 removing a file on linux from windows program

We have a server on windows, but it has a network drive which is actually on a linux server. The Program has to delete a file at the same location with the same name (signals), it works ok when those files are on local drive, but when running on the network drive, it will sometime not delete the file, and even worse, the functions will return that everything went ok(meaning the file is deleted). I tried with remove, _unlink, DeleteFileA , the problem still persists,sometime completely at random it won't be deleted and it will stay like this.
The code is really simple:
bool File::Delete()
{
if(isFile() && exist())
{
return DeleteFileA(filename.c_str()) != 0 ? true : false;
}
else
return false;
}
This will always return true even if the file is not removed, if for example it would not have permission it should fail(and fail each time, not at random), could someone give me an idea ? I ran out of options :(
Edit:
Thanks to #ExcessPhase, it seems like moveFile actually detects an error, so renaming before deleting can detect a problem "ERROR_FILE_NOT_FOUND".
Other things : This random problem can only happen when the files are created from linux server. If I create them from windows, they will always be deleted. Even more: If I have a file that the program cannot delete, and I create another file next to it from Windows, the program will detect and delete the one it could not delete before.
Edit2: Closer to answer: filename test and TEST in linux is different, while in Windows it's the same. The problem seems to appear at random when the case don't match. But I'm not sure since it's so random.
I believe the problem is with Samba service on Linux, which implements the SMB protocol for Windows. DeleteFile function just requests the SMB server (Server service on Windows) to delete a file. The success is returned by Samba.
Maybe you should try something more higher level like boost file system, or std::experimental::filesystem::remove

CMake Release made my code stop working properly

I have a C++ program which works well when I compile with no additional options. However, whenever I use cmake -DCMAKE_BUILD_TYPE=Release there is a very specific part of the code which stops working.
Concretely, I have an interface for a Boost Fibonacci Heap. I am calling this function:
narrow_band_.push(myObject);
And this function does the following:
inline void myHeap::push (myStruct & c) {
handles_[c.getIndex()] = heap_.push(c);
}
where heap_ is:
boost::heap::fibonacci_heap<myStruct, boost::heap::compare<compare_func>> heap_;
For some reason the heap_size is not being modified, therefore the rest of the code does not work because the next step is to extract the minimum from the heap and it is always empty.
In Debug mode it works ok. Thank you for your help.
EDIT - Additional info
I have also found this problem: I have a set of code which do simple math operations. In Release mode the results are incorrect. If I just do cout of a couple of variables to check their values, the result changes, which is still incorrect but quite strange.
I solved the problem. It was funny but in this case it was not initialization/timing issues common in the Release mode. It was that the return statement was missing in a couple of functions. In debug mode the output was perfect but in the release mode that failed. I had warnings deactivated, that is why I did not see it before.

Diagnosing QDir::rmdir failure

I’m using the following code to delete an empty folder on Linux:
bool removeFolder (const QString& path)
{
QDir dir(path);
assert(dir.exists());
return dir.rmdir(".");
}
For some reason it sometimes returns false (for specific folders, but those folders don’t seem to be wrong in any way). If I subsequently use ::rmdir from <unistd.h> to remove the same folder, it succeeds.
How can I tell why QDir::rmdir is failing?
This never happened on Windows so far, QDir::rmdir just works.
Confirming: works on windown, fails on linux.
Reading the "rmdir" doc in <unistd>, here https://pubs.opengroup.org/onlinepubs/007904875/functions/rmdir.html, it says there that "If the path argument refers to a path whose final component is either dot or dot-dot, rmdir() shall fail." So what's probably happening is that QDir::rmdir() is calling the unistd rmdir() function in linux, and this one fails with ".".
I tried to just use the full absolute path ( QDir::rmdir(absolutePath) ) and it worked; however, i see basically no point in using QDir::rmdir() over unistd's rmdir(), so i''ll stick w/ the unistd rmdir() from now on.
note: QDir::removeRecursively() is a different story: it seems to work okay, and it's way more convenient than going through opendir() and then successive readdir()'s (or the nftw(...FTW_DEPTH...) thingie).
I had the same problem but on Windows, I could not delete an empty directory with QDir().rmdir(path);. This happened on some older hard drive so may be the ancient file system was to blame. But I found a hack:
QFile(path).setPermissions(QFile::WriteOther); // this works even for dirs
bool success = QDir().rmdir(path);
Of course, you should revert the permissions back to original values if the deletion was unsuccessful anyway, but that's a different story.
Try to use this one:
dir.rmdir(dir.absolutePath())