-
Notifications
You must be signed in to change notification settings - Fork 672
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
cygwin bash cannot delete directories on mirror drive #269
Comments
I found a problem. The ntdelete example fails silently if the directory isn't empty. And also cygwing rmdir says "Permission denied" instead of "directory not empty". So, maybe MirrorCreateFile() schould check to see if the Directory is being opened for DELETE, and if so then call MirrorDeleteDirectory() to make sure it's empty. If it does this before the directory is opened with DELETE, then FindFirstFile() won't fail. That would look like this
I tried that. And now ntdelete.cpp, cmd rmdir and cygwin rmdir all seem to be working and getting "directory not empty" if the directory isn't. However, I noticed winfstest is failing two tests
So it looks like the Windows API does prevent DeleteFile() from being called on directories (returns lasterr = 5, access denied). But if mirror doesn't allow this, then cygwin rmdir won't work. Is this checking DELETE in the current mirror.c maybe a thing that was done to pass winfstest? Maybe there is some way to detect if we're getting there from Windows API DeleteFile() and return the error in that case? |
The same behaviour (no failure when trying to delete non-empty directories) applies to the FUSE mirror |
From looking at the cygwin source, I can see that it seems always use FILE_OPEN_FOR_BACKUP_INTENT when opening a directory in order to delete it. But we don't get it in the CreateOptions in MirrorOpenFile() when the directory is opened for DELETE. From looking at the dokan sys and dll code, I don't see where dokan is making that flag disappear. It could be that my updated-today cygwin is different from what's in their current git source. If we got that flag in mirror, then I think it would know that it was OK to open the directory DELETE. |
Hi @bailey27 ,
This line is made in prupose (probably can be improved) to fix DeleteFile on Directory that success without it About flag If you find nothing until there, I will make a test myself on cygwin this weekend :) |
ntdelete.cpp passes FILE_OPEN_FOR_BACKUP_INTENT to NtOpenFile() but it never makes it to dokany. And the same with cygwin when it does rmdir which ends up in unlink_nt() in cygwin1.dll. I stepped through unlink_nt() in gdb and saw it. I built the driver with some more debug prints and put a DbgBreakPoint() if the file is opened with DELETE. I saw there in the kernel debugger that there wasn't any FILE_OPEN_FOR_BACKUP_INTENT. It really looks to me like some driver upstream of dokan1.sys or maybe the OS itself is stripping out FILE_OPEN_FOR_BACKUP_INTENT from the IRP. I tried it with signed dokan1.sys, signed dokan1.dll, and my fs's .exe self-signed (with cert installed) and running it as administrator, and still no FILE_OPEN_FOR_BACKUP_INTENT makes it through. |
Maybe you could ask them to fix cygwin? This seems to work
|
😮 So just by adding the flag as you did in your patch
does work ? Humm could you try to add here https://github.com/dokan-dev/dokany/blob/master/samples/dokan_mirror/mirror.c#L257
and see if it helps ? |
Yes, if I add the flag in cygwin, then it works. I tried your code in an original mirror.c and also in my fs (which I haven't changed about this yet) and it doesn't help. It looks like the problem is that without the FILE_FLAG_BACKUP_SEMANTICS, then the CreateFile() still fails to open the directory. I think your code would make it so that when the directory does get deleted (assuming most of my changes are put in), then the MirrorDeleteDirectory() will be called automatically and the MirrorCleanup() will delete the directory like it should. So I think it would eliminate the need for a lot of the changes that I suggested. But I think, assuming it is true that somebody's clearing the FILE_OPEN_FOR_BACKUP_INTENT bit, then I think there's no way to allow cygwin as it is to delete directories and also prevent somebody from doing DeleteFile("c:\empty_dir"). I think this FILE_OPEN_FOR_BACKUP_INTENT / FILE_FLAG_BACKUP_SEMANTICS flag is like some way to be able to open the file or directory for delete without caring about whether it's a file or a directory. But I think maybe MS didn't intend for it to be used like that. Or maybe they don't anymore. It sounds to me like FILE_OPEN_FOR_BACKUP_INTENT has something to do with security, and maybe that's whey the OS or some driver is preventing dokany from getting it. I tried a registry hack to prevent any Windows Defender stuff from loading in my VM. I could see that after that then WdFilter.sys wasn't loaded in the filter stack. But still I got no FILE_OPEN_FOR_BACKUP_INTENT passed through to dokany. So I think it isn't the defender filter driver that's clearing the bit. |
@Rondom I have added an issue for the behaviour that you describe: #270 C Mirror does have the check here: dokany/samples/dokan_mirror/mirror.c Line 757 in bbbdbcb
|
If I add CreateOptions |= FILE_DIRECTORY_FILE to your change, then it makes cygwin work all by itself.
If that code is there, then what happens in mirror is I think exactly like what happens if cygwin uses FILE_DIRECTORY_FILE like how I changed it in cygwin. What happens then is that mirror gets into this code
And it opens the directory like this
So it opens it with desiredAccess 0 and read and write sharing. And because DokanFileInfo->IsDirectory is true, then when cygwin calls NtSetInformationFile() to delete the directory and then MirroDeleteDirectory() is called, then FindFirstFile() doesn't fail because there is no sharing violation because the directory was opened differently than specified (without DELETE and with sharing). But, this causes a third winfstest failure.
It fails because it opens the directory for read with no sharing and then tries to open it for read again and unexpectedly succeeds. I take back everything I said about FILE_FLAG_BACKUP_SEMANTICS maybe not really being intended by MS for opening directories like files. I saw somewhere that they say it is for that. But, I'm wondering if Dokany ever got FILE_OPEN_FOR_BACKUP_INTENT in an Irp before? If it was getting that, then why was this code needed (from the real current mirror.c)?
I mean the part about or-ing in FILE_FLAG_BACKUP_SEMANTICS. I think if somebody tried to open a directory as a file on purpose with CreateFile(), they would specify FILE_FLAG_BACKUP_SEMANTICS, and if that was getting to Dokany via FILE_OPEN_FOR_BACKUP_INTENT in the driver, then it would work without having to or in the flag in MirrorCreateFile(). |
Hi @bailey27 , I have currently a fix in local for your issue but I have the issue with Current patch: http://pastebin.com/pyAsVeRp |
EDIT: FILE_FLAG_BACKUP_SEMANTICS has to be added otherwise explorer would get an acces denied for every open folde request. I also dont know if dokan sys does never get I agree normally if a software want to open a folder, he should use
|
Current fix on my side: Liryna@dd175d2 |
All test pass for me with Liryna@2ecbb93 |
I tried it, and cygwin rmdir didn't work
I got in the debug output
I think PathIsDirectoryEmpty() has the same sharing problem as FindFirstFile(). I tried this
And now cygwin rmdir works and all the winfstest tests still pass. Do you think it's OK to put the FILE_SHARE_READ there if they open the directory for DELETE? |
😮 I was sure that PathIsDirectoryEmpty didn't had this issue, that why I replaced it 😢 Would be great to find a solution to see if directory is empty without having this sharing violation. |
During Delete we have |
For my fs, I need to treat a directory as empty if it has only a gocryptfs.diriv file in it. It wouldn't help me to know only if the directory is really empty or not. |
Thanks for fixing it. |
I'm reviewing this code for #210 and the current solution, while it seems to resolve the issue with cygwin, does not seem to be the proper solution. Based on the documentation for |
Why not log this information in the create.c to see if the driver get's this information? if (irpSp->Parameters.Create.Options & FILE_OPEN_FOR_BACKUP_INTENT) { |
There is only It mean that dokan does have to handle himself @Corillian , how do you think this should be handled ? |
I put that print in the driver. I deleted a directory with cygwin and my own ntdelete.cpp, which I know uses this flag to open the file, and I didn't see the print. @Corillian, I think it might be possible to deduce from the security attributes/descriptor that this flag was used for the create. But I think finding it out would be hard. And also, it might not be definitive. Those settings, however unusual, might be there in a case in which the caller didn't actually specify FILE_OPEN_FOR_BACKUP_INTENT. What is it that you don't like about the current solution? I think some of the complexity is there to prevent DeleteFile("m:\empty_dir") from working. If that works, then one of the winfs tests fails. |
This is really weird. I ran filespy. The first time I tried it, I saw FILE_OPEN_FOR_BACKUP_INTENT in the IRP_MJ_CREATE when cygwin opened the the dir on C: to delete it. I tried it on my mirror drive, and the flag was not there. I saved a screenshot of this, so I know it like that. Then I shut down my VM and added a new disk to it so I could try it with fat32. Now I don't see the flag on c: (ntfs), f: (fat32) or m: (mirror). Also, I found out that when you do a CreateFile(), if you don't specify FILE_FLAG_BACKUP_SEMANTICS, then win32 automatically adds FILE_NON_DIRECTORY_FILE. If you use NtOpenFile() to open a dir for delete, then you don't even need FILE_OPEN_FOR_BACKUP_INTENT to open it, and you can still delete it. |
If FILE_OPEN_FOR_BACKUP_INTENT is represented in the security flags and the security descriptor then all mirror should have to do is pass those flags and the descriptor to Too bad MS hasn't open sourced their NTFS driver =( |
Environment
Check List
Description
If you use cygwin bash on a mirror drive and try to mkdir a directory and then try to rmdir it, you get "Permission denied".
The problem seems to be that cygwin is using the native NT APIs.
The way it deletes a directory is by opening it with NtOpenFile() with access DELETE, then calling NtSetInformationFile() to set the disposition to delete on close (DELETE_ON_CLOSE).
mirror.c has three problems with this.
attributes, and if it's a directory, then call MirrorDeleteDirectory().
It looks like MirroDeleteFile() can't call MirroDeleteDirectory() if the thing is a directory, because then, because DELETE_ON_CLOSE has bee set, the FindFirstFile() will fail.
So I'm not sure if there's any way to make it behave exactly like the native fs. In the native fs, calling NtSetInfomationFile() to set the delete on close will fail if the target is an non-empty directory.
I've attached a program "ntdelete.cpp" that deletes a file or directory like cygwin does (or at least very much like cygwin). This program can delete a directory on a native fs but not on a mirror.
It looks to me like Windows doesn't really have any problem with DELETE being specified when opening a directory. And it looks like, for whatever reason, DokanFileInfo->IsDirectory isn't necessary true even if the file is a directory.
Here are the changes I think should be made to mirror.c to make it work with cygwin rmdir and ntdelete.cpp.
Logs
Here is ntdelete.cpp in a zip file.
ntdelete.zip
The text was updated successfully, but these errors were encountered: