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

Update PowerShellRunner to support hard link creation and enable more functional tests on Mac #266

Merged
5 changes: 0 additions & 5 deletions GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,6 @@ public BashRunner()
}
}

public override bool SupportsHardlinkCreation
{
get { return true; }
}

protected override string FileName
{
get
Expand Down
5 changes: 0 additions & 5 deletions GVFS/GVFS.FunctionalTests/FileSystemRunners/CmdRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,6 @@ public class CmdRunner : ShellRunner
"The process cannot access the file because it is being used by another process"
};

public override bool SupportsHardlinkCreation
{
get { return true; }
}

protected override string FileName
{
get
Expand Down
11 changes: 1 addition & 10 deletions GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,6 @@ public static FileSystemRunner DefaultRunner
get { return defaultRunner; }
}

public virtual bool SupportsHardlinkCreation
{
get { return false; }
}

// File methods
public abstract bool FileExists(string path);
public abstract string MoveFile(string sourcePath, string targetPath);
Expand All @@ -74,11 +69,7 @@ public virtual bool SupportsHardlinkCreation
public abstract void ReadAllText_FileShouldNotBeFound(string path);

public abstract void CreateEmptyFile(string path);

public virtual void CreateHardLink(string newLinkFilePath, string existingFilePath)
{
Assert.Fail($"This runner does not support {nameof(this.CreateHardLink)}");
}
public abstract void CreateHardLink(string newLinkFilePath, string existingFilePath);

/// <summary>
/// Write the specified contents to the specified file. By calling this method the caller is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ public override void CreateEmptyFile(string path)
this.RunProcess(string.Format("-Command \"&{{ New-Item -ItemType file {0}}}\"", path));
}

public override void CreateHardLink(string newLinkFilePath, string existingFilePath)
{
this.RunProcess(string.Format("-Command \"&{{ New-Item -ItemType HardLink -Path {0} -Value {1}}}\"", newLinkFilePath, existingFilePath));
}

public override void WriteAllText(string path, string contents)
{
this.RunProcess(string.Format("-Command \"&{{ Out-File -FilePath {0} -InputObject '{1}' -Encoding ascii -NoNewline}}\"", path, contents));
Expand Down
21 changes: 21 additions & 0 deletions GVFS/GVFS.FunctionalTests/FileSystemRunners/SystemIORunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ public override void CreateEmptyFile(string path)
}
}

public override void CreateHardLink(string newLinkFilePath, string existingFilePath)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
WindowsCreateHardLink(newLinkFilePath, existingFilePath, IntPtr.Zero).ShouldBeTrue($"Failed to create hard link: {Marshal.GetLastWin32Error()}");
}
else
{
MacCreateHardLink(existingFilePath, newLinkFilePath).ShouldEqual(0, $"Failed to create hard link: {Marshal.GetLastWin32Error()}");
}
}

public override void WriteAllText(string path, string contents)
{
File.WriteAllText(path, contents);
Expand Down Expand Up @@ -180,6 +192,15 @@ public override void ReadAllText_FileShouldNotBeFound(string path)
[DllImport("kernel32", SetLastError = true)]
private static extern bool MoveFileEx(string existingFileName, string newFileName, int flags);

[DllImport("libc", EntryPoint = "link", SetLastError = true)]
private static extern int MacCreateHardLink(string oldPath, string newPath);

[DllImport("kernel32.dll", EntryPoint = "CreateHardLink", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool WindowsCreateHardLink(
string newLinkFileName,
string existingFileName,
IntPtr securityAttributes);

private static void RetryOnException(Action action)
{
for (int i = 0; i < 10; i++)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,6 @@ public void CreateFileTest()
[TestCase, Order(2)]
public void CreateHardLinkTest()
{
if (!this.fileSystem.SupportsHardlinkCreation)
{
return;
}

string existingFileName = "fileToLinkTo.txt";
string existingFilePath = this.Enlistment.GetVirtualPathTo(existingFileName);
GVFSHelpers.ModifiedPathsShouldNotContain(this.fileSystem, this.Enlistment.DotGVFSRoot, existingFileName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using GVFS.Tests.Should;
using NUnit.Framework;
using System.IO;
using System.Runtime.InteropServices;

namespace GVFS.FunctionalTests.Tests.EnlistmentPerTestCase
{
Expand Down Expand Up @@ -119,7 +118,7 @@ public void ModifiedPathsSavedAfterRemount(FileSystemRunner fileSystem)
}
}

[TestCaseSource(typeof(HardLinkRunners), HardLinkRunners.TestRunners)]
[TestCaseSource(typeof(FileSystemRunner), FileSystemRunner.TestRunners)]
public void ModifiedPathsCorrectAfterHardLinking(FileSystemRunner fileSystem)
{
const string ExpectedModifiedFilesContentsAfterHardlinks =
Expand Down Expand Up @@ -161,27 +160,5 @@ A LinkToFileOutsideSrc.txt
reader.ReadToEnd().ShouldEqual(ExpectedModifiedFilesContentsAfterHardlinks);
}
}

private class HardLinkRunners
{
public const string TestRunners = "Runners";

public static object[] Runners
{
get
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return new[]
{
new object[] { new CmdRunner() },
new object[] { new BashRunner() },
};
}

return new[] { new object[] { new BashRunner() } };
}
}
}
}
}
7 changes: 1 addition & 6 deletions GVFS/GVFS.FunctionalTests/Tests/GitCommands/AddStageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,6 @@ public void StageBasicTest()
[TestCase, Order(3)]
public void AddAndStageHardLinksTest()
{
if (!this.FileSystem.SupportsHardlinkCreation)
{
return;
}

this.CreateHardLink("ReadmeLink.md", "Readme.md");
this.ValidateGitCommand("add ReadmeLink.md");
this.RunGitCommand("commit -m \"Created ReadmeLink.md\"");
Expand All @@ -61,7 +56,7 @@ public void StageAllowsPlaceholderCreation()
private void CommandAllowsPlaceholderCreation(string command, params string[] fileToReadPathParts)
{
string fileToRead = Path.Combine(fileToReadPathParts);
this.EditFile($"Some new content for {command}.", "Readme.md");
this.EditFile($"Some new content for {command}.", "Protocol.md");
ManualResetEventSlim resetEvent = GitHelpers.RunGitCommandWithWaitAndStdIn(this.Enlistment, resetTimeout: 3000, command: $"{command} -p", stdinToQuit: "q", processId: out _);
this.FileContentsShouldMatch(fileToRead);
this.ValidateGitCommand("--no-optional-locks status");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,6 @@ public void CheckoutBranchAfterReadingAllFilesAndVerifyContentsCorrect()
}

[TestCase]
[Category(Categories.MacTODO.M3)]
public void CheckoutBranchThatHasFolderShouldGetDeleted()
{
// this.ControlGitRepo.Commitish should not have the folder Test_ConflictTests\AddedFiles
Expand Down Expand Up @@ -803,6 +802,7 @@ public void SuccessfullyChecksOutDirectoryToFileToDirectory()
this.ShouldNotExistOnDisk("d", "c");
}

// TODO(Mac): This test needs the fix for issue #264
[TestCase]
[Category(Categories.MacTODO.M3)]
public void DeleteFileThenCheckout()
Expand Down
10 changes: 2 additions & 8 deletions GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -224,14 +224,8 @@ protected void CreateHardLink(string newLinkFileName, string existingFileName)
string virtualNewLinkFile = Path.Combine(this.Enlistment.RepoRoot, newLinkFileName);
string controlNewLinkFile = Path.Combine(this.ControlGitRepo.RootPath, newLinkFileName);

// GitRepoTests are only run with SystemIORunner (which does not support hardlink
// creation) so use a BashRunner instead.
this.FileSystem.SupportsHardlinkCreation.ShouldBeFalse(
"If this.FileSystem.SupportsHardlinkCreation is true, CreateHardLink no longer needs to create a BashRunner");
FileSystemRunner runner = new BashRunner();

runner.CreateHardLink(virtualNewLinkFile, virtualExistingFile);
runner.CreateHardLink(controlNewLinkFile, controlExistingFile);
this.FileSystem.CreateHardLink(virtualNewLinkFile, virtualExistingFile);
this.FileSystem.CreateHardLink(controlNewLinkFile, controlExistingFile);
}

protected void SetFileAsReadOnly(string filePath)
Expand Down
1 change: 0 additions & 1 deletion GVFS/GVFS.FunctionalTests/Tests/GitCommands/RebaseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ namespace GVFS.FunctionalTests.Tests.GitCommands
{
[TestFixture]
[Category(Categories.GitCommands)]
[Category(Categories.MacTODO.M3)]
public class RebaseTests : GitRepoTests
{
public RebaseTests() : base(enlistmentPerTest: true)
Expand Down
33 changes: 26 additions & 7 deletions ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ static bool TrySendRequestAndWaitForResponse(
const VirtualizationRoot* root,
MessageType messageType,
const vnode_t vnode,
const char* vnodePath,
int pid,
const char* procname,
int* kauthResult,
Expand Down Expand Up @@ -253,14 +254,25 @@ static int HandleVnodeOperation(
vnode_t currentVnode = reinterpret_cast<vnode_t>(arg1);
// arg2 is the (vnode_t) parent vnode
int* kauthError = reinterpret_cast<int*>(arg3);
int kauthResult = KAUTH_RESULT_DEFER;

const char* vnodePath = nullptr;
char vnodePathBuffer[PrjFSMaxPath];
int vnodePathLength = PrjFSMaxPath;

VirtualizationRoot* root = nullptr;
vtype vnodeType;
uint32_t currentVnodeFileFlags;
int pid;
char procname[MAXCOMLEN + 1];

int kauthResult = KAUTH_RESULT_DEFER;

// TODO(Mac): Issue #271 - Reduce reliance on vn_getpath
// Call vn_getpath first when the cache is hottest to increase the chances
// of successfully getting the path
if (0 == vn_getpath(currentVnode, vnodePathBuffer, &vnodePathLength))
{
vnodePath = vnodePathBuffer;
}

if (!ShouldHandleVnodeOpEvent(
context,
Expand Down Expand Up @@ -292,6 +304,7 @@ static int HandleVnodeOperation(
root,
MessageType_KtoU_EnumerateDirectory,
currentVnode,
vnodePath,
pid,
procname,
&kauthResult,
Expand All @@ -308,6 +321,7 @@ static int HandleVnodeOperation(
root,
MessageType_KtoU_NotifyDirectoryPreDelete,
currentVnode,
vnodePath,
pid,
procname,
&kauthResult,
Expand All @@ -325,6 +339,7 @@ static int HandleVnodeOperation(
root,
MessageType_KtoU_NotifyFilePreDelete,
currentVnode,
vnodePath,
pid,
procname,
&kauthResult,
Expand All @@ -351,6 +366,7 @@ static int HandleVnodeOperation(
root,
MessageType_KtoU_HydrateFile,
currentVnode,
vnodePath,
pid,
procname,
&kauthResult,
Expand Down Expand Up @@ -428,6 +444,7 @@ static int HandleFileOpOperation(
root,
messageType,
currentVnodeFromPath,
newPath,
pid,
procname,
&kauthResult,
Expand All @@ -439,7 +456,7 @@ static int HandleFileOpOperation(
else if (KAUTH_FILEOP_CLOSE == action)
{
vnode_t currentVnode = reinterpret_cast<vnode_t>(arg0);
// arg1 is the (const char *) path
const char* path = (const char*)arg1;
int closeFlags = static_cast<int>(arg2);

if (vnode_isdir(currentVnode))
Expand Down Expand Up @@ -476,6 +493,7 @@ static int HandleFileOpOperation(
root,
MessageType_KtoU_NotifyFileModified,
currentVnode,
path,
pid,
procname,
&kauthResult,
Expand All @@ -492,6 +510,7 @@ static int HandleFileOpOperation(
root,
MessageType_KtoU_NotifyFileCreated,
currentVnode,
path,
pid,
procname,
&kauthResult,
Expand Down Expand Up @@ -647,6 +666,7 @@ static bool TrySendRequestAndWaitForResponse(
const VirtualizationRoot* root,
MessageType messageType,
const vnode_t vnode,
const char* vnodePath,
int pid,
const char* procname,
int* kauthResult,
Expand All @@ -657,11 +677,10 @@ static bool TrySendRequestAndWaitForResponse(
OutstandingMessage message;
message.receivedResponse = false;

char vnodePath[PrjFSMaxPath];
int vnodePathLength = PrjFSMaxPath;
if (vn_getpath(vnode, vnodePath, &vnodePathLength))
if (nullptr == vnodePath)
{
KextLog_Error("Unable to resolve a vnode to its path");
// Default error code is EACCES. See errno.h for more codes.
*kauthError = EAGAIN;
*kauthResult = KAUTH_RESULT_DENY;
return false;
}
Expand Down