Windows NtDll.cs requires `FILE_FLAG_BACKUP_SEMANTICS` for directories
Current behaviour
GetLockingProcessInfos with LockManagerFeatures.UseLowLevelApi does not work if the path is a directory on windows.
The simplest example is getting the locks of a directory that is the working directory of a cmd/powershell window.
Environment info
Windows 10 x64 on .NET 7
Possible solution
I've found this line from the documentation: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
| Flag | Meaning |
|---|---|
| FILE_FLAG_BACKUP_SEMANTICS 0x02000000 |
You must set this flag to obtain a handle to a directory. A directory handle can be passed to some functions instead of a file handle. For more information, see the Remarks section. |
And then later in the same docs:
To open a directory using CreateFile, specify the FILE_FLAG_BACKUP_SEMANTICS flag as part of dwFlagsAndAttributes. Appropriate security checks still apply when this flag is used without SE_BACKUP_NAME and SE_RESTORE_NAME privileges.
So I think we should be using this flag if the path is a directory 👍
Other notes
I would be interested to know how this compares to the approach mentioned by @KirillOsenkov in #6. The only case I can think of is that it would also be able to find locks on children of the directory, whereas this approach would not. Finding the lock of a child of a directory seems to be outside the scope of this library though.
Additionally, Restart Manager seems to not support finding locks on directories. Seems like another problem, but I thought I would mention it here.
I am interested in fixing it here because I'm currently using my fork of LockCheck in https://github.com/domsleee/ForceOps. For directories, I am currently just using NtDll (with UseLowLevelApi).
I added a snippet into my fork, something along these lines:
public static IEnumerable<ProcessInformation> GetProcessesWithCurrentDirectory(string directory)
{
if (string.IsNullOrWhiteSpace(directory))
{
return Array.Empty<ProcessInformation>();
}
var list = new List<ProcessInformation>();
var processes = ProcessInformation.GetProcesses();
foreach (var process in processes)
{
if (process.CurrentDirectory is string currentDirectory)
{
if (currentDirectory.StartsWith(directory, StringComparison.OrdinalIgnoreCase))
{
list.Add(process);
}
}
}
return list;
}
And here's a sample of getting the current directory for any given process: https://github.com/KirillOsenkov/Misc/blob/cf1b6ece2be91459bb301f65eac5b10e2f398562/GetProcessCurrentDirectory.cs#L16