Skip to content

Commit

Permalink
Merge pull request #87 from narasamdya/dev/narasamdya/Work/FixDirEnum…
Browse files Browse the repository at this point in the history
…SimpProvider

Fix directory enumeration callback in Simple provider
  • Loading branch information
cgallred authored Mar 16, 2022
2 parents aae0337 + 4975c35 commit 840e778
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 56 deletions.
67 changes: 37 additions & 30 deletions ProjectedFSLib.Managed.Test/BasicTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

namespace ProjectedFSLib.Managed.Test
{
Expand Down Expand Up @@ -126,6 +124,14 @@ public void TestCanReadThroughVirtualizationRoot(string destinationFile)
Assert.That("RandomNonsense", Is.Not.EqualTo(line));
}

#if NETCOREAPP3_1_OR_GREATER
// Running this test in NET framework causes CI failures in the Win 2022 version.
// They fail because the .NET Framework 4.8 version of the fixed Simple provider trips over the platform bug.
// The .NET Core 3.1 one works fine. Evidently Framework and Core enumerate differently, with Framework using
// a buffer that is small enough to hit the platform bug.
//
// The CI Win 2019 version doesn't run the symlink tests at all, since symlink support isn't in that version of ProjFS.

// We start the virtualization instance in each test case, so that exercises the following
// methods in Microsoft.Windows.ProjFS:
// VirtualizationInstance.VirtualizationInstance()
Expand Down Expand Up @@ -197,37 +203,30 @@ public void TestCanReadSymlinksThroughVirtualizationRoot(string destinationFile,
// IRequiredCallbacks.StartDirectoryEnumeration()
// IRequiredCallbacks.GetDirectoryEnumeration()
// IRequiredCallbacks.EndDirectoryEnumeration()
[TestCase("dir1\\dir2\\dir3\\sourcebar.txt", "dir4\\dir5\\dir6\\symbar.txt", "..\\..\\..\\dir1\\dir2\\dir3\\sourcebar.txt", Category = SymlinkTestCategory)]
public void TestCanReadSymlinksWithRelativePathTargetsThroughVirtualizationRoot(string destinationFile, string symlinkFile, string symlinkTarget)
[TestCase("dir1\\dir2\\dir3\\", "file.txt", "dir4\\dir5\\sdir6", Category = SymlinkTestCategory)]
public void TestCanReadSymlinkDirsThroughVirtualizationRoot(string destinationDir, string destinationFileName, string symlinkDir)
{
helpers.StartTestProvider(out string sourceRoot, out string virtRoot);

// Some contents to write to the file in the source and read out through the virtualization.
string fileContent = nameof(TestCanReadSymlinksThroughVirtualizationRoot);
string fileContent = nameof(TestCanReadSymlinkDirsThroughVirtualizationRoot);

// Create a file and a symlink to it.
string destinationFile = Path.Combine(destinationDir, destinationFileName);
helpers.CreateVirtualFile(destinationFile, fileContent);
helpers.CreateVirtualSymlink(symlinkFile, symlinkTarget, false);

// Open the file through the virtualization and read its contents.
string line = helpers.ReadFileInVirtRoot(destinationFile);
Assert.That(fileContent, Is.EqualTo(line));
helpers.CreateVirtualSymlinkDirectory(symlinkDir, destinationDir, true);

// Enumerate and ensure the symlink is present.
var pathToEnumerate = Path.Combine(virtRoot, Path.GetDirectoryName(symlinkFile));
var pathToEnumerate = Path.Combine(virtRoot, Path.GetDirectoryName(symlinkDir));
DirectoryInfo virtDirInfo = new DirectoryInfo(pathToEnumerate);
List<FileSystemInfo> virtList = new List<FileSystemInfo>(virtDirInfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories));
string fullPath = Path.Combine(virtRoot, symlinkFile);
FileSystemInfo symlink = virtList.Where(x => x.FullName == fullPath).First();
Assert.That((symlink.Attributes & FileAttributes.ReparsePoint) != 0);

// Get the symlink target and check that it points to the correct file.
string reparsePointTarget = helpers.ReadReparsePointTargetInVirtualRoot(symlinkFile);
Assert.That(reparsePointTarget, Is.EqualTo(symlinkTarget));
string fullPath = Path.Combine(virtRoot, symlinkDir);

// Check if we have the same content if accessing the file through a symlink.
// Ensure we can access the file through directory symlink.
string symlinkFile = Path.Combine(virtRoot, symlinkDir, destinationFileName);
string lineAccessedThroughSymlink = helpers.ReadFileInVirtRoot(symlinkFile);
Assert.That(fileContent, Is.EqualTo(lineAccessedThroughSymlink));
}
#endif

// We start the virtualization instance in each test case, so that exercises the following
// methods in Microsoft.Windows.ProjFS:
Expand All @@ -247,26 +246,34 @@ public void TestCanReadSymlinksWithRelativePathTargetsThroughVirtualizationRoot(
// IRequiredCallbacks.StartDirectoryEnumeration()
// IRequiredCallbacks.GetDirectoryEnumeration()
// IRequiredCallbacks.EndDirectoryEnumeration()
[TestCase("dir1\\dir2\\dir3\\", "file.txt", "dir4\\dir5\\sdir6", Category = SymlinkTestCategory)]
public void TestCanReadSymlinkDirsThroughVirtualizationRoot(string destinationDir, string destinationFileName, string symlinkDir)
[TestCase("dir1\\dir2\\dir3\\sourcebar.txt", "dir4\\dir5\\dir6\\symbar.txt", "..\\..\\..\\dir1\\dir2\\dir3\\sourcebar.txt", Category = SymlinkTestCategory)]
public void TestCanReadSymlinksWithRelativePathTargetsThroughVirtualizationRoot(string destinationFile, string symlinkFile, string symlinkTarget)
{
helpers.StartTestProvider(out string sourceRoot, out string virtRoot);

// Some contents to write to the file in the source and read out through the virtualization.
string fileContent = nameof(TestCanReadSymlinkDirsThroughVirtualizationRoot);
string fileContent = nameof(TestCanReadSymlinksWithRelativePathTargetsThroughVirtualizationRoot);

string destinationFile = Path.Combine(destinationDir, destinationFileName);
// Create a file and a symlink to it.
helpers.CreateVirtualFile(destinationFile, fileContent);
helpers.CreateVirtualSymlinkDirectory(symlinkDir, destinationDir, true);
helpers.CreateVirtualSymlink(symlinkFile, symlinkTarget, false);

// Open the file through the virtualization and read its contents.
string line = helpers.ReadFileInVirtRoot(destinationFile);
Assert.That(fileContent, Is.EqualTo(line));

// Enumerate and ensure the symlink is present.
var pathToEnumerate = Path.Combine(virtRoot, Path.GetDirectoryName(symlinkDir));
var pathToEnumerate = Path.Combine(virtRoot, Path.GetDirectoryName(symlinkFile));
DirectoryInfo virtDirInfo = new DirectoryInfo(pathToEnumerate);
List<FileSystemInfo> virtList = new List<FileSystemInfo>(virtDirInfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories));
string fullPath = Path.Combine(virtRoot, symlinkDir);
string fullPath = Path.Combine(virtRoot, symlinkFile);
FileSystemInfo symlink = virtList.Where(x => x.FullName == fullPath).First();
Assert.That((symlink.Attributes & FileAttributes.ReparsePoint) != 0);

// Ensure we can access the file through directory symlink.
string symlinkFile = Path.Combine(virtRoot, symlinkDir, destinationFileName);
// Get the symlink target and check that it points to the correct file.
string reparsePointTarget = helpers.ReadReparsePointTargetInVirtualRoot(symlinkFile);
Assert.That(reparsePointTarget, Is.EqualTo(symlinkTarget));

// Check if we have the same content if accessing the file through a symlink.
string lineAccessedThroughSymlink = helpers.ReadFileInVirtRoot(symlinkFile);
Assert.That(fileContent, Is.EqualTo(lineAccessedThroughSymlink));
}
Expand Down
9 changes: 1 addition & 8 deletions simpleProviderManaged/ActiveEnumeration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@ public ActiveEnumeration(List<ProjectedFileInfo> fileInfos)
this.MoveNext();
}

/// <summary>
/// Indicates whether the current item is the first one in the enumeration.
/// </summary>
public bool IsCurrentFirst { get; private set; }

/// <summary>
/// true if Current refers to an element in the enumeration, false if Current is past the end of the collection
/// </summary>
Expand Down Expand Up @@ -59,8 +54,7 @@ public void RestartEnumeration(
public bool MoveNext()
{
this.IsCurrentValid = this.fileInfoEnumerator.MoveNext();
this.IsCurrentFirst = false;


while (this.IsCurrentValid && this.IsCurrentHidden())
{
this.IsCurrentValid = this.fileInfoEnumerator.MoveNext();
Expand Down Expand Up @@ -146,7 +140,6 @@ private bool IsCurrentHidden()
private void ResetEnumerator()
{
this.fileInfoEnumerator = this.fileInfos.GetEnumerator();
this.IsCurrentFirst = true;
}
}
}
Expand Down
42 changes: 30 additions & 12 deletions simpleProviderManaged/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

using CommandLine;
using Serilog;
using System;

using System;

namespace SimpleProviderManaged
{
public class Program
Expand All @@ -20,17 +20,35 @@ public static int Main(string[] args)
{
try
{
// We want verbose logging so we can see all our callback invocations.
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.WriteTo.File("SimpleProviderManaged-.log", rollingInterval: RollingInterval.Day)
.CreateLogger();

Log.Information("Start");

var parserResult = Parser.Default
var parser = new Parser(with =>
{
with.AutoHelp = true;
with.AutoVersion = true;
with.EnableDashDash = true;
with.CaseSensitive = false;
with.CaseInsensitiveEnumValues = true;
with.HelpWriter = Console.Out;
});

var parserResult = parser
.ParseArguments<ProviderOptions>(args)
.WithParsed((ProviderOptions options) => Run(options));
.WithParsed((ProviderOptions options) =>
{
// We want verbose logging so we can see all our callback invocations.
var logConfig = new LoggerConfiguration()
.WriteTo.Console()
.WriteTo.File("SimpleProviderManaged-.log", rollingInterval: RollingInterval.Day);

if (options.Verbose)
{
logConfig = logConfig.MinimumLevel.Verbose();
}

Log.Logger = logConfig.CreateLogger();

Log.Information("Start");
Run(options);
});

Log.Information("Exit successfully");
return (int) ReturnCode.Success;
Expand Down
3 changes: 3 additions & 0 deletions simpleProviderManaged/ProviderOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ public class ProviderOptions
[Option('n', "notifications", HelpText = "Enable file system operation notifications.")]
public bool EnableNotifications { get; set; }

[Option('v', "verbose", HelpText = "Use verbose log level.")]
public bool Verbose { get; set; }

[Option('d', "denyDeletes", HelpText = "Deny deletes.", Hidden = true)]
public bool DenyDeletes { get; set; }

Expand Down
17 changes: 11 additions & 6 deletions simpleProviderManaged/SimpleProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using System.IO;
using System.Threading;
using Microsoft.Windows.ProjFS;
using System.Runtime.InteropServices;

namespace SimpleProviderManaged
{
Expand Down Expand Up @@ -390,6 +389,7 @@ internal HResult GetDirectoryEnumerationCallback(
enumeration.TrySaveFilterString(filterFileName);
}

int numEntriesAdded = 0;
HResult hr = HResult.Ok;

while (enumeration.IsCurrentValid)
Expand All @@ -409,28 +409,33 @@ internal HResult GetDirectoryEnumerationCallback(
// remembers the entry it couldn't add simply by not advancing its ActiveEnumeration.
if (AddFileInfoToEnum(enumResult, fileInfo, targetPath))
{
Log.Verbose("----> GetDirectoryEnumerationCallback Added {Entry} {Kind} {Target}", fileInfo.Name, fileInfo.IsDirectory, targetPath);

++numEntriesAdded;
enumeration.MoveNext();
}
else
{
// If we could not add the very first entry in the enumeration, a provider must
// return InsufficientBuffer.
if (enumeration.IsCurrentFirst)
Log.Verbose("----> GetDirectoryEnumerationCallback NOT added {Entry} {Kind} {Target}", fileInfo.Name, fileInfo.IsDirectory, targetPath);

if (numEntriesAdded == 0)
{
hr = HResult.InsufficientBuffer;
}

break;
}
}

if (hr == HResult.Ok)
{
Log.Information("<---- GetDirectoryEnumerationCallback {Result}", hr);
Log.Information("<---- GetDirectoryEnumerationCallback {Result} [Added entries: {EntryCount}]", hr, numEntriesAdded);
}
else
{
Log.Error("<---- GetDirectoryEnumerationCallback {Result}", hr);
Log.Error("<---- GetDirectoryEnumerationCallback {Result} [Added entries: {EntryCount}]", hr, numEntriesAdded);
}

return hr;
}

Expand Down

0 comments on commit 840e778

Please sign in to comment.