Diagnosing QDir::rmdir failure - c++

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())

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

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

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).

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

Get proper case working directory?

To better familiarize myself with C++, I'm redoing an old college OS assignment: program your own shell. I'm using all sorts of Windows.h that I've never known existed. So far I've made good progress but I've noticed something about my cd implementation and my working directory I get back from getcwd.
My cd command does some error checking but ultimately it comes down to chdir(path). Say I'm at C:\ and there exists a folder FOLDER. If I use chdir("folder") then later when I call getcwd(dir, FILENAME_MAX) then I'll get the string C:\folder instead of the case correct string C:\FOLDER. How can I retrieve the working directory with every folder having the proper case?
Note: When I first start my shell and run my pwd command (that solely prints dir from my getcwd call), I get a path that is properly cased. As soon as I start changing the working directory then the casing always matches my strings instead of the actual folder casing.
I think the Windows command prompt just uses GetLongPathName, which returns the path with appropriate casing (however, it doesn't change the drive letter's casing).
If you want an uppercase drive letter, the GetShortPathName function returns the short path with the driver letter capitalized. You can then pass this short path to GetLongPathName, which will turn it into a properly cased long path, but this isn't what cmd does.
You can also use SHGetFileInfo, but it's not the easiest approach.
You can use the GetFullPathName API function to return the proper (case correct) path of the current directory, as in the following example:
TCHAR tchPath[MAX_PATH];
GetFullPathName(TEXT("."), MAX_PATH, tchPath, NULL);

when to call _findclose?

Note: Since problem is solved, I've added comments to my original posts.
According to "http://msdn.microsoft.com/en-us/library/6tkkkc1y%28v=vs.90%29.aspx", it stated as this:
*You must call _findclose after you are finished using either the _findfirst or _findnext function (or any variants). This frees up resources used by these functions in your application.*
--comment: it is vague, but what microsoft is trying to say is: some users just need to find the first file(they don't need to call _findnext), then call _findclose; some users called _findnext (they MUST have already called _findfirst), after finished using that, call _findclose. Actually _findnext can be called multiple times, while _findclose is only responsible to a handle, which is created by _findfirst.
And following is a piece of code that is widely used to list files in the directory. -- comment: it is correct.
For example, if there are 2 files and 1 directory in the directory, then:
.
..
ddd
file1.txt
file2.txt
_findfirst is called once. the handle's corresponding fileinfo is system directory "." (is that right?)
--comment: no. the handle is a group of files+directories, the fileinfo is acting as the "cursor". (fileinfo always contained the "name" field, I bet the implementation of _findnext is using the "name" to find the next in a group of files+directories specified by the handle)
_findnext is called 4 times. (the first argument is always the handle corresponding to ".", is that right?)
--comment: yes + no. The first argument is always the same handle; the handle is NOT corresponding to any fileinfo, but to a group of them.
My questions are:
Does "_findclose" be called ONCE is enough?
*--comment:* yes.
if _findnext will not change the handle value, how can it "remember" where to start to find the next file(or directory)? (sorry, maybe I was thinking in the "linked list" pattern.)
*--comment:* I bet is using fileinfo's name field. Just as in Windows Explorer, we sort the contents in a folder, given a file name, we can know their position in the list, so we can "find next".
Are there any harm to call _findclose more times than needed? (like crash or something)
*--comment:* a stupid question. Sorry!
Or is the following code wrong at all? If yes, what's the correct way to implement it?
--It is correct code.
// List the files in the directory
intptr_t file;
_finddata_t filedata;
file = _findfirst(desc.c_str(),&filedata);
if (file != -1)
{
do
{
cout << filedata.name << endl;
// Or put the file name in a vector here
} while (_findnext(file,&filedata) == 0);
}
else
{
cout << "No described files found" << endl;
}
_findclose(file);
I asked this because I've met an issue that an application is freezing a directory which can not be deleted if the process is alive. However, I can guaranteed that "_findclose" is called on every return value from "_findfirst". If I add "_findclose" after calling "_findnext", then will fix the issue perfectly. How can you help me to explain it?
--comment: pardon. don't use "guarantee" too easy. That's where the bug is.
Note: I don't have problem to understand what is a handle, like open a file, read/write/read/write..., close the file handle. I just find the documentations describing these three APIs are vague.
--comment: go to improve your english.
Thank you in advance.
Your calls to _findclose should match with your calls to _findfirst -- i.e., each time you call _findfirst, you should have a matching call to _findclose.
In the code above, since you have only one call to _findfirst, it's correct to have only one call to _findclose.
If you were doing a recursive search of subdirectories, then you'd end up with multiple calls to _findfirst as you descend the hierarchy, and matching calls to _findclose as you finish and ascend back up the hierarchy.
You only need to call _findclose once, when you are finished.
On Windows, a directory may be locked if it is the current directory of your process. Try calling _chdir.
If that doesn't work... are you opening any of the files in the directory you're searching? An open file may lock the directory as well.
It may be useful to let Process Explorer get a look at your app. It can tell you for sure what handle you have left open.