diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs index 95c325876f..cd1a79337a 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs @@ -53,11 +53,6 @@ public BashRunner() } } - public override bool SupportsHardlinkCreation - { - get { return true; } - } - protected override string FileName { get diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/CmdRunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/CmdRunner.cs index cb5184c066..fd40bef8a9 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/CmdRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/CmdRunner.cs @@ -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 diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs index b8a2f9cc63..e064be50ab 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs @@ -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); @@ -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); /// /// Write the specified contents to the specified file. By calling this method the caller is diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/PowerShellRunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/PowerShellRunner.cs index ce38b68619..5a640ec598 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/PowerShellRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/PowerShellRunner.cs @@ -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)); diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/SystemIORunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/SystemIORunner.cs index 096d4ab3ff..e0fac59e42 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/SystemIORunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/SystemIORunner.cs @@ -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); @@ -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++) diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs index 3100e7ad07..eca57a1c29 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs @@ -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); diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerTestCase/ModifiedPathsTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerTestCase/ModifiedPathsTests.cs index 3e9a651879..c57bffccd2 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerTestCase/ModifiedPathsTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerTestCase/ModifiedPathsTests.cs @@ -4,7 +4,6 @@ using GVFS.Tests.Should; using NUnit.Framework; using System.IO; -using System.Runtime.InteropServices; namespace GVFS.FunctionalTests.Tests.EnlistmentPerTestCase { @@ -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 = @@ -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() } }; - } - } - } } } diff --git a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/AddStageTests.cs b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/AddStageTests.cs index e8a36a23c5..d4e834dad4 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/AddStageTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/AddStageTests.cs @@ -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\""); @@ -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"); diff --git a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/CheckoutTests.cs b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/CheckoutTests.cs index 30f0f02bcd..728218341f 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/CheckoutTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/CheckoutTests.cs @@ -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 @@ -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() diff --git a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs index 932ee1eaa7..9200be7c55 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs @@ -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) diff --git a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/RebaseTests.cs b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/RebaseTests.cs index 72b1726d60..4f7a62926d 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/RebaseTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/RebaseTests.cs @@ -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) diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp index acce65bce3..6868047a9d 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp @@ -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, @@ -253,14 +254,25 @@ static int HandleVnodeOperation( vnode_t currentVnode = reinterpret_cast(arg1); // arg2 is the (vnode_t) parent vnode int* kauthError = reinterpret_cast(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, @@ -292,6 +304,7 @@ static int HandleVnodeOperation( root, MessageType_KtoU_EnumerateDirectory, currentVnode, + vnodePath, pid, procname, &kauthResult, @@ -308,6 +321,7 @@ static int HandleVnodeOperation( root, MessageType_KtoU_NotifyDirectoryPreDelete, currentVnode, + vnodePath, pid, procname, &kauthResult, @@ -325,6 +339,7 @@ static int HandleVnodeOperation( root, MessageType_KtoU_NotifyFilePreDelete, currentVnode, + vnodePath, pid, procname, &kauthResult, @@ -351,6 +366,7 @@ static int HandleVnodeOperation( root, MessageType_KtoU_HydrateFile, currentVnode, + vnodePath, pid, procname, &kauthResult, @@ -428,6 +444,7 @@ static int HandleFileOpOperation( root, messageType, currentVnodeFromPath, + newPath, pid, procname, &kauthResult, @@ -439,7 +456,7 @@ static int HandleFileOpOperation( else if (KAUTH_FILEOP_CLOSE == action) { vnode_t currentVnode = reinterpret_cast(arg0); - // arg1 is the (const char *) path + const char* path = (const char*)arg1; int closeFlags = static_cast(arg2); if (vnode_isdir(currentVnode)) @@ -476,6 +493,7 @@ static int HandleFileOpOperation( root, MessageType_KtoU_NotifyFileModified, currentVnode, + path, pid, procname, &kauthResult, @@ -492,6 +510,7 @@ static int HandleFileOpOperation( root, MessageType_KtoU_NotifyFileCreated, currentVnode, + path, pid, procname, &kauthResult, @@ -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, @@ -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; }