Skip to content
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

fix(windows): NtDll.cs does not work for directories without FILE_FLAG_BACKUP_SEMANTICS #8

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions LockCheck.Tests/LockManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,16 @@ public void LockInformationAvailable(LockManagerFeatures features)
StringAssert.Contains(processInfos[0].ExecutableName?.ToLowerInvariant(), process.ProcessName.ToLowerInvariant());
});
}

[DataTestMethod]
public void LockInformationAvailableForDirectoryWithNtDll()
{
TestHelper.CreateFolderWithOpenedProcess((tempFolder, process) =>
{
var processInfosUsingNtDll = LockManager.GetLockingProcessInfos(new[] { tempFolder }, LockManagerFeatures.UseLowLevelApi).ToList();
Assert.AreEqual(1, processInfosUsingNtDll.Count);
Assert.AreEqual(process.Id, processInfosUsingNtDll[0].ProcessId);
});
}
}
}
73 changes: 72 additions & 1 deletion LockCheck.Tests/TestHelper.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace LockCheck.Tests
{
internal static class TestHelper
{
{
public static void CreateLockSituation(Action<Exception, string> action)
{
string tempFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N") + ".test");
Expand Down Expand Up @@ -34,5 +38,72 @@ public static void CreateLockSituation(Action<Exception, string> action)
}
}

public static void CreateFolderWithOpenedProcess(Action<string, Process> action)
{
var tempFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N") + ".testdir");
Directory.CreateDirectory(tempFolder);
var processInfo = LaunchPowershellInDirectory(tempFolder)!;

try
{
action(tempFolder, processInfo.process);
}
finally
{
processInfo.Dispose();

if (Directory.Exists(tempFolder))
{
Directory.Delete(tempFolder);
}
}
}

static ProcessInfo LaunchPowershellInDirectory(string workingDirectory)
{
ProcessStartInfo startInfo = new ProcessStartInfo();
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = "powershell",
WorkingDirectory = workingDirectory,
Arguments = "-NoProfile -Command \"echo 'process has been loaded'; sleep 10\"",
RedirectStandardOutput = true,
CreateNoWindow = true
}
};

var output = new List<string>();
process.OutputDataReceived += (sender, e) =>
{
if (e.Data != null) output.Add(e.Data);
};
process.Start();
process.BeginOutputReadLine();

var startTime = DateTime.Now;
while (!(output.LastOrDefault() ?? "").EndsWith("process has been loaded") && !process.HasExited)
{
Thread.Sleep(50);
if (DateTime.Now.Subtract(startTime).TotalSeconds > 2)
{
throw new Exception("Gave up after waiting 2 seconds");
}
}

return new ProcessInfo() { process = process };
}

class ProcessInfo: IDisposable
{
public Process process { get; set; }

public void Dispose()
{
process.Kill();
process.WaitForExit();
}
}
}
}
2 changes: 1 addition & 1 deletion LockCheck/LockCheck.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

<ItemGroup>
<PackageReference Include="System.Security.Principal.Windows" Version="4.7.0" />
<PackageReference Include="Nerdbank.GitVersioning" Version="3.0.50" PrivateAssets="all" />
<PackageReference Include="Nerdbank.GitVersioning" Version="3.6.133" PrivateAssets="all" />
</ItemGroup>

</Project>
15 changes: 15 additions & 0 deletions LockCheck/Windows/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -301,5 +301,20 @@ internal static SafeFileHandle GetFileHandle(string name)
(int)FileAttributes.Normal,
IntPtr.Zero);
}

// From `CreateFile` (`CreateFileA`) documentation:
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
const int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;

internal static SafeFileHandle GetDirectoryHandle(string name)
{
return CreateFile(name,
0, // "FileAccess.Neither" Read nor Write
FileShare.Read | FileShare.Write | FileShare.Delete,
IntPtr.Zero,
FileMode.Open,
(int)FileAttributes.Normal | FILE_FLAG_BACKUP_SEMANTICS,
IntPtr.Zero);
}
}
}
11 changes: 10 additions & 1 deletion LockCheck/Windows/NtDll.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;

namespace LockCheck.Windows
Expand Down Expand Up @@ -32,7 +34,7 @@ private static void GetLockingProcessInfo(string path, List<ProcessInfo> result)

try
{
using (var handle = NativeMethods.GetFileHandle(path))
using (var handle = GetFileOrDirectoryHandle(path))
{
if (handle.IsInvalid)
{
Expand Down Expand Up @@ -99,6 +101,13 @@ private static Exception GetException(uint status, string apiName, string messag

return new NtException(res, status, $"{message} ({apiName}() status {status} (0x{status:8X})");
}

private static SafeFileHandle GetFileOrDirectoryHandle(string path)
{
return Directory.Exists(path)
? NativeMethods.GetDirectoryHandle(path)
: NativeMethods.GetFileHandle(path);
}
}

public class NtException : Win32Exception
Expand Down