Skip to content

Commit 840e778

Browse files
authored
Merge pull request #87 from narasamdya/dev/narasamdya/Work/FixDirEnumSimpProvider
Fix directory enumeration callback in Simple provider
2 parents aae0337 + 4975c35 commit 840e778

File tree

5 files changed

+82
-56
lines changed

5 files changed

+82
-56
lines changed

ProjectedFSLib.Managed.Test/BasicTests.cs

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@
44
using NUnit.Framework;
55
using System;
66
using System.Collections.Generic;
7-
using System.Diagnostics;
87
using System.IO;
98
using System.Linq;
109
using System.Runtime.InteropServices;
1110
using System.Text;
12-
using System.Threading;
1311

1412
namespace ProjectedFSLib.Managed.Test
1513
{
@@ -126,6 +124,14 @@ public void TestCanReadThroughVirtualizationRoot(string destinationFile)
126124
Assert.That("RandomNonsense", Is.Not.EqualTo(line));
127125
}
128126

127+
#if NETCOREAPP3_1_OR_GREATER
128+
// Running this test in NET framework causes CI failures in the Win 2022 version.
129+
// They fail because the .NET Framework 4.8 version of the fixed Simple provider trips over the platform bug.
130+
// The .NET Core 3.1 one works fine. Evidently Framework and Core enumerate differently, with Framework using
131+
// a buffer that is small enough to hit the platform bug.
132+
//
133+
// The CI Win 2019 version doesn't run the symlink tests at all, since symlink support isn't in that version of ProjFS.
134+
129135
// We start the virtualization instance in each test case, so that exercises the following
130136
// methods in Microsoft.Windows.ProjFS:
131137
// VirtualizationInstance.VirtualizationInstance()
@@ -197,37 +203,30 @@ public void TestCanReadSymlinksThroughVirtualizationRoot(string destinationFile,
197203
// IRequiredCallbacks.StartDirectoryEnumeration()
198204
// IRequiredCallbacks.GetDirectoryEnumeration()
199205
// IRequiredCallbacks.EndDirectoryEnumeration()
200-
[TestCase("dir1\\dir2\\dir3\\sourcebar.txt", "dir4\\dir5\\dir6\\symbar.txt", "..\\..\\..\\dir1\\dir2\\dir3\\sourcebar.txt", Category = SymlinkTestCategory)]
201-
public void TestCanReadSymlinksWithRelativePathTargetsThroughVirtualizationRoot(string destinationFile, string symlinkFile, string symlinkTarget)
206+
[TestCase("dir1\\dir2\\dir3\\", "file.txt", "dir4\\dir5\\sdir6", Category = SymlinkTestCategory)]
207+
public void TestCanReadSymlinkDirsThroughVirtualizationRoot(string destinationDir, string destinationFileName, string symlinkDir)
202208
{
203209
helpers.StartTestProvider(out string sourceRoot, out string virtRoot);
210+
204211
// Some contents to write to the file in the source and read out through the virtualization.
205-
string fileContent = nameof(TestCanReadSymlinksThroughVirtualizationRoot);
212+
string fileContent = nameof(TestCanReadSymlinkDirsThroughVirtualizationRoot);
206213

207-
// Create a file and a symlink to it.
214+
string destinationFile = Path.Combine(destinationDir, destinationFileName);
208215
helpers.CreateVirtualFile(destinationFile, fileContent);
209-
helpers.CreateVirtualSymlink(symlinkFile, symlinkTarget, false);
210-
211-
// Open the file through the virtualization and read its contents.
212-
string line = helpers.ReadFileInVirtRoot(destinationFile);
213-
Assert.That(fileContent, Is.EqualTo(line));
216+
helpers.CreateVirtualSymlinkDirectory(symlinkDir, destinationDir, true);
214217

215218
// Enumerate and ensure the symlink is present.
216-
var pathToEnumerate = Path.Combine(virtRoot, Path.GetDirectoryName(symlinkFile));
219+
var pathToEnumerate = Path.Combine(virtRoot, Path.GetDirectoryName(symlinkDir));
217220
DirectoryInfo virtDirInfo = new DirectoryInfo(pathToEnumerate);
218221
List<FileSystemInfo> virtList = new List<FileSystemInfo>(virtDirInfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories));
219-
string fullPath = Path.Combine(virtRoot, symlinkFile);
220-
FileSystemInfo symlink = virtList.Where(x => x.FullName == fullPath).First();
221-
Assert.That((symlink.Attributes & FileAttributes.ReparsePoint) != 0);
222-
223-
// Get the symlink target and check that it points to the correct file.
224-
string reparsePointTarget = helpers.ReadReparsePointTargetInVirtualRoot(symlinkFile);
225-
Assert.That(reparsePointTarget, Is.EqualTo(symlinkTarget));
222+
string fullPath = Path.Combine(virtRoot, symlinkDir);
226223

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

232231
// We start the virtualization instance in each test case, so that exercises the following
233232
// methods in Microsoft.Windows.ProjFS:
@@ -247,26 +246,34 @@ public void TestCanReadSymlinksWithRelativePathTargetsThroughVirtualizationRoot(
247246
// IRequiredCallbacks.StartDirectoryEnumeration()
248247
// IRequiredCallbacks.GetDirectoryEnumeration()
249248
// IRequiredCallbacks.EndDirectoryEnumeration()
250-
[TestCase("dir1\\dir2\\dir3\\", "file.txt", "dir4\\dir5\\sdir6", Category = SymlinkTestCategory)]
251-
public void TestCanReadSymlinkDirsThroughVirtualizationRoot(string destinationDir, string destinationFileName, string symlinkDir)
249+
[TestCase("dir1\\dir2\\dir3\\sourcebar.txt", "dir4\\dir5\\dir6\\symbar.txt", "..\\..\\..\\dir1\\dir2\\dir3\\sourcebar.txt", Category = SymlinkTestCategory)]
250+
public void TestCanReadSymlinksWithRelativePathTargetsThroughVirtualizationRoot(string destinationFile, string symlinkFile, string symlinkTarget)
252251
{
253252
helpers.StartTestProvider(out string sourceRoot, out string virtRoot);
254-
255253
// Some contents to write to the file in the source and read out through the virtualization.
256-
string fileContent = nameof(TestCanReadSymlinkDirsThroughVirtualizationRoot);
254+
string fileContent = nameof(TestCanReadSymlinksWithRelativePathTargetsThroughVirtualizationRoot);
257255

258-
string destinationFile = Path.Combine(destinationDir, destinationFileName);
256+
// Create a file and a symlink to it.
259257
helpers.CreateVirtualFile(destinationFile, fileContent);
260-
helpers.CreateVirtualSymlinkDirectory(symlinkDir, destinationDir, true);
258+
helpers.CreateVirtualSymlink(symlinkFile, symlinkTarget, false);
259+
260+
// Open the file through the virtualization and read its contents.
261+
string line = helpers.ReadFileInVirtRoot(destinationFile);
262+
Assert.That(fileContent, Is.EqualTo(line));
261263

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

268-
// Ensure we can access the file through directory symlink.
269-
string symlinkFile = Path.Combine(virtRoot, symlinkDir, destinationFileName);
272+
// Get the symlink target and check that it points to the correct file.
273+
string reparsePointTarget = helpers.ReadReparsePointTargetInVirtualRoot(symlinkFile);
274+
Assert.That(reparsePointTarget, Is.EqualTo(symlinkTarget));
275+
276+
// Check if we have the same content if accessing the file through a symlink.
270277
string lineAccessedThroughSymlink = helpers.ReadFileInVirtRoot(symlinkFile);
271278
Assert.That(fileContent, Is.EqualTo(lineAccessedThroughSymlink));
272279
}

simpleProviderManaged/ActiveEnumeration.cs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,6 @@ public ActiveEnumeration(List<ProjectedFileInfo> fileInfos)
1919
this.MoveNext();
2020
}
2121

22-
/// <summary>
23-
/// Indicates whether the current item is the first one in the enumeration.
24-
/// </summary>
25-
public bool IsCurrentFirst { get; private set; }
26-
2722
/// <summary>
2823
/// true if Current refers to an element in the enumeration, false if Current is past the end of the collection
2924
/// </summary>
@@ -59,8 +54,7 @@ public void RestartEnumeration(
5954
public bool MoveNext()
6055
{
6156
this.IsCurrentValid = this.fileInfoEnumerator.MoveNext();
62-
this.IsCurrentFirst = false;
63-
57+
6458
while (this.IsCurrentValid && this.IsCurrentHidden())
6559
{
6660
this.IsCurrentValid = this.fileInfoEnumerator.MoveNext();
@@ -146,7 +140,6 @@ private bool IsCurrentHidden()
146140
private void ResetEnumerator()
147141
{
148142
this.fileInfoEnumerator = this.fileInfos.GetEnumerator();
149-
this.IsCurrentFirst = true;
150143
}
151144
}
152145
}

simpleProviderManaged/Program.cs

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
using CommandLine;
55
using Serilog;
6-
using System;
7-
6+
using System;
7+
88
namespace SimpleProviderManaged
99
{
1010
public class Program
@@ -20,17 +20,35 @@ public static int Main(string[] args)
2020
{
2121
try
2222
{
23-
// We want verbose logging so we can see all our callback invocations.
24-
Log.Logger = new LoggerConfiguration()
25-
.WriteTo.Console()
26-
.WriteTo.File("SimpleProviderManaged-.log", rollingInterval: RollingInterval.Day)
27-
.CreateLogger();
28-
29-
Log.Information("Start");
30-
31-
var parserResult = Parser.Default
23+
var parser = new Parser(with =>
24+
{
25+
with.AutoHelp = true;
26+
with.AutoVersion = true;
27+
with.EnableDashDash = true;
28+
with.CaseSensitive = false;
29+
with.CaseInsensitiveEnumValues = true;
30+
with.HelpWriter = Console.Out;
31+
});
32+
33+
var parserResult = parser
3234
.ParseArguments<ProviderOptions>(args)
33-
.WithParsed((ProviderOptions options) => Run(options));
35+
.WithParsed((ProviderOptions options) =>
36+
{
37+
// We want verbose logging so we can see all our callback invocations.
38+
var logConfig = new LoggerConfiguration()
39+
.WriteTo.Console()
40+
.WriteTo.File("SimpleProviderManaged-.log", rollingInterval: RollingInterval.Day);
41+
42+
if (options.Verbose)
43+
{
44+
logConfig = logConfig.MinimumLevel.Verbose();
45+
}
46+
47+
Log.Logger = logConfig.CreateLogger();
48+
49+
Log.Information("Start");
50+
Run(options);
51+
});
3452

3553
Log.Information("Exit successfully");
3654
return (int) ReturnCode.Success;

simpleProviderManaged/ProviderOptions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ public class ProviderOptions
2121
[Option('n', "notifications", HelpText = "Enable file system operation notifications.")]
2222
public bool EnableNotifications { get; set; }
2323

24+
[Option('v', "verbose", HelpText = "Use verbose log level.")]
25+
public bool Verbose { get; set; }
26+
2427
[Option('d', "denyDeletes", HelpText = "Deny deletes.", Hidden = true)]
2528
public bool DenyDeletes { get; set; }
2629

simpleProviderManaged/SimpleProvider.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
using System.IO;
1010
using System.Threading;
1111
using Microsoft.Windows.ProjFS;
12-
using System.Runtime.InteropServices;
1312

1413
namespace SimpleProviderManaged
1514
{
@@ -390,6 +389,7 @@ internal HResult GetDirectoryEnumerationCallback(
390389
enumeration.TrySaveFilterString(filterFileName);
391390
}
392391

392+
int numEntriesAdded = 0;
393393
HResult hr = HResult.Ok;
394394

395395
while (enumeration.IsCurrentValid)
@@ -409,28 +409,33 @@ internal HResult GetDirectoryEnumerationCallback(
409409
// remembers the entry it couldn't add simply by not advancing its ActiveEnumeration.
410410
if (AddFileInfoToEnum(enumResult, fileInfo, targetPath))
411411
{
412+
Log.Verbose("----> GetDirectoryEnumerationCallback Added {Entry} {Kind} {Target}", fileInfo.Name, fileInfo.IsDirectory, targetPath);
413+
414+
++numEntriesAdded;
412415
enumeration.MoveNext();
413416
}
414417
else
415418
{
416-
// If we could not add the very first entry in the enumeration, a provider must
417-
// return InsufficientBuffer.
418-
if (enumeration.IsCurrentFirst)
419+
Log.Verbose("----> GetDirectoryEnumerationCallback NOT added {Entry} {Kind} {Target}", fileInfo.Name, fileInfo.IsDirectory, targetPath);
420+
421+
if (numEntriesAdded == 0)
419422
{
420423
hr = HResult.InsufficientBuffer;
421424
}
425+
422426
break;
423427
}
424428
}
425429

426430
if (hr == HResult.Ok)
427431
{
428-
Log.Information("<---- GetDirectoryEnumerationCallback {Result}", hr);
432+
Log.Information("<---- GetDirectoryEnumerationCallback {Result} [Added entries: {EntryCount}]", hr, numEntriesAdded);
429433
}
430434
else
431435
{
432-
Log.Error("<---- GetDirectoryEnumerationCallback {Result}", hr);
436+
Log.Error("<---- GetDirectoryEnumerationCallback {Result} [Added entries: {EntryCount}]", hr, numEntriesAdded);
433437
}
438+
434439
return hr;
435440
}
436441

0 commit comments

Comments
 (0)