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