From 9088c0466efd6d78186938802248fc9b97b62e44 Mon Sep 17 00:00:00 2001 From: William Baker Date: Tue, 14 Aug 2018 08:13:34 -0700 Subject: [PATCH 01/10] Mac: Add file renamed notification --- .../EnlistmentPerFixture/GitFilesTests.cs | 20 +++--- .../GitMoveRenameTests.cs | 31 ++++++--- .../MoveRenameFileTests.cs | 12 ++-- .../MoveRenameFileTests_2.cs | 20 +++--- .../MacFileSystemVirtualizer.cs | 19 ++++-- .../WindowsFileSystemVirtualizer.cs | 30 +-------- .../FileSystem/FileSystemVirtualizer.cs | 33 ++++++++++ .../PrjFSKext/PrjFSKext/KauthHandler.cpp | 65 ++++++++++++++++++- .../PrjFSKext/VirtualizationRoots.hpp | 2 +- ProjFS.Mac/PrjFSKext/public/Message.h | 2 + .../PrjFSLib.Mac.Managed/CallbackDelegates.cs | 4 +- .../VirtualizationInstance.cs | 12 ++-- ProjFS.Mac/PrjFSLib/PrjFSLib.cpp | 22 ++++--- 13 files changed, 187 insertions(+), 85 deletions(-) diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs index c2fac6926..1f8d91d38 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs @@ -187,18 +187,17 @@ public void ModifiedFileWillGetAddedToModifiedPathsFile() } [TestCase, Order(8)] - [Category(Categories.Mac.M2TODO)] - public void RenamedFileAddedToSparseCheckoutAndSkipWorktreeBitCleared() + public void RenamedFileAddedToModifiedPathsFile() { + GitMoveRenameTests.IgnoreSystemIOMoveOnMac(this.fileSystem); + string fileToRenameEntry = "Test_EPF_MoveRenameFileTests/ChangeUnhydratedFileName/Program.cs"; string fileToRenameTargetEntry = "Test_EPF_MoveRenameFileTests/ChangeUnhydratedFileName/Program2.cs"; - string fileToRenameRelativePath = "Test_EPF_MoveRenameFileTests\\ChangeUnhydratedFileName\\Program.cs"; - string fileToRenameTargetRelativePath = "Test_EPF_MoveRenameFileTests\\ChangeUnhydratedFileName\\Program2.cs"; this.VerifyWorktreeBit(fileToRenameEntry, LsFilesStatus.SkipWorktree); this.fileSystem.MoveFile( - this.Enlistment.GetVirtualPathTo(fileToRenameRelativePath), - this.Enlistment.GetVirtualPathTo(fileToRenameTargetRelativePath)); + this.Enlistment.GetVirtualPathTo(fileToRenameEntry), + this.Enlistment.GetVirtualPathTo(fileToRenameTargetEntry)); this.Enlistment.WaitForBackgroundOperations().ShouldEqual(true, "Background operations failed to complete."); GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, fileToRenameEntry); @@ -209,19 +208,16 @@ public void RenamedFileAddedToSparseCheckoutAndSkipWorktreeBitCleared() } [TestCase, Order(9)] - [Category(Categories.Mac.M2TODO)] - public void RenamedFileAndOverwrittenTargetAddedToSparseCheckoutAndSkipWorktreeBitCleared() + public void RenamedFileAndOverwrittenTargetAddedToModifiedPathsFile() { string fileToRenameEntry = "Test_EPF_MoveRenameFileTests_2/MoveUnhydratedFileToOverwriteUnhydratedFileAndWrite/RunUnitTests.bat"; string fileToRenameTargetEntry = "Test_EPF_MoveRenameFileTests_2/MoveUnhydratedFileToOverwriteUnhydratedFileAndWrite/RunFunctionalTests.bat"; - string fileToRenameRelativePath = "Test_EPF_MoveRenameFileTests_2\\MoveUnhydratedFileToOverwriteUnhydratedFileAndWrite\\RunUnitTests.bat"; - string fileToRenameTargetRelativePath = "Test_EPF_MoveRenameFileTests_2\\MoveUnhydratedFileToOverwriteUnhydratedFileAndWrite\\RunFunctionalTests.bat"; this.VerifyWorktreeBit(fileToRenameEntry, LsFilesStatus.SkipWorktree); this.VerifyWorktreeBit(fileToRenameTargetEntry, LsFilesStatus.SkipWorktree); this.fileSystem.ReplaceFile( - this.Enlistment.GetVirtualPathTo(fileToRenameRelativePath), - this.Enlistment.GetVirtualPathTo(fileToRenameTargetRelativePath)); + this.Enlistment.GetVirtualPathTo(fileToRenameEntry), + this.Enlistment.GetVirtualPathTo(fileToRenameTargetEntry)); this.Enlistment.WaitForBackgroundOperations().ShouldEqual(true, "Background operations failed to complete."); GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, fileToRenameEntry); diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitMoveRenameTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitMoveRenameTests.cs index d0b1a09e4..30f1e4ae3 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitMoveRenameTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitMoveRenameTests.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture { @@ -21,6 +22,15 @@ public GitMoveRenameTests(FileSystemRunner fileSystem) this.fileSystem = fileSystem; } + public static void IgnoreSystemIOMoveOnMac(FileSystemRunner runner) + { + if (runner is SystemIORunner && + RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Assert.Ignore("TODO(Mac): SystemIORunner rename tests require hardlink notifications"); + } + } + [TestCase, Order(1)] public void GitStatus() { @@ -37,6 +47,7 @@ public void GitStatusAfterNewFile() string filename = "new.cs"; string filePath = this.Enlistment.GetVirtualPathTo(filename); + filePath.ShouldNotExistOnDisk(this.fileSystem); this.fileSystem.WriteAllText(filePath, this.testFileContents); filePath.ShouldBeAFile(this.fileSystem).WithContents(this.testFileContents); @@ -52,14 +63,14 @@ public void GitStatusAfterNewFile() } [TestCase, Order(3)] - [Category(Categories.Mac.M2TODO)] public void GitStatusAfterFileNameCaseChange() { string oldFilename = "new.cs"; this.EnsureTestFileExists(oldFilename); string newFilename = "New.cs"; - this.fileSystem.MoveFile(this.Enlistment.GetVirtualPathTo(oldFilename), this.Enlistment.GetVirtualPathTo(newFilename)); + string newFilePath = this.Enlistment.GetVirtualPathTo(newFilename); + this.fileSystem.MoveFile(this.Enlistment.GetVirtualPathTo(oldFilename), newFilePath); GitHelpers.CheckGitCommandAgainstGVFSRepo( this.Enlistment.RepoRoot, @@ -67,17 +78,21 @@ public void GitStatusAfterFileNameCaseChange() "On branch " + Properties.Settings.Default.Commitish, "Untracked files:", newFilename); + + this.fileSystem.DeleteFile(newFilePath); } [TestCase, Order(4)] - [Category(Categories.Mac.M2TODO)] public void GitStatusAfterFileRename() { + IgnoreSystemIOMoveOnMac(this.fileSystem); + string oldFilename = "New.cs"; - this.Enlistment.GetVirtualPathTo(oldFilename).ShouldBeAFile(this.fileSystem); + this.EnsureTestFileExists(oldFilename); string newFilename = "test.cs"; - this.fileSystem.MoveFile(this.Enlistment.GetVirtualPathTo(oldFilename), this.Enlistment.GetVirtualPathTo(newFilename)); + string newFilePath = this.Enlistment.GetVirtualPathTo(newFilename); + this.fileSystem.MoveFile(this.Enlistment.GetVirtualPathTo(oldFilename), newFilePath); GitHelpers.CheckGitCommandAgainstGVFSRepo( this.Enlistment.RepoRoot, @@ -178,9 +193,10 @@ public void GitWithEnvironmentVariables() } [TestCase, Order(9)] - [Category(Categories.Mac.M2TODO)] public void GitStatusAfterRenameFileIntoRepo() { + IgnoreSystemIOMoveOnMac(this.fileSystem); + string filename = "GitStatusAfterRenameFileIntoRepo.cs"; // Create the test file in this.Enlistment.EnlistmentRoot as it's outside of src @@ -190,7 +206,7 @@ public void GitStatusAfterRenameFileIntoRepo() this.fileSystem.WriteAllText(filePath, this.testFileContents); filePath.ShouldBeAFile(this.fileSystem).WithContents(this.testFileContents); - string renamedFileName = "GVFlt_MoveFileTest\\GitStatusAfterRenameFileIntoRepo.cs"; + string renamedFileName = Path.Combine("GVFlt_MoveFileTest", "GitStatusAfterRenameFileIntoRepo.cs"); this.fileSystem.MoveFile(filePath, this.Enlistment.GetVirtualPathTo(renamedFileName)); this.Enlistment.GetVirtualPathTo(filePath).ShouldNotExistOnDisk(this.fileSystem); @@ -219,7 +235,6 @@ public void GitStatusAfterRenameFileOutOfRepo() } [TestCase, Order(11)] - [Category(Categories.Mac.M2TODO)] public void GitStatusAfterRenameFolderIntoRepo() { string folderName = "GitStatusAfterRenameFolderIntoRepo"; diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/MoveRenameFileTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/MoveRenameFileTests.cs index 5ea5b7ab0..43af93dc6 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/MoveRenameFileTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/MoveRenameFileTests.cs @@ -10,7 +10,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture // TODO 452590 - Combine all of the MoveRenameTests into a single fixture, and have each use different // well known files [TestFixtureSource(typeof(FileSystemRunner), FileSystemRunner.TestRunners)] - [Category(Categories.Mac.M2TODO)] + [Category(Categories.Mac.M2)] public class MoveRenameFileTests : TestsWithEnlistmentPerFixture { public const string TestFileContents = @@ -53,8 +53,8 @@ public MoveRenameFileTests(FileSystemRunner fileSystem) [TestCase] public void ChangeUnhydratedFileName() { - string oldFilename = "Test_EPF_MoveRenameFileTests\\ChangeUnhydratedFileName\\Program.cs"; - string newFilename = "Test_EPF_MoveRenameFileTests\\ChangeUnhydratedFileName\\renamed_Program.cs"; + string oldFilename = Path.Combine("Test_EPF_MoveRenameFileTests", "ChangeUnhydratedFileName", "Program.cs"); + string newFilename = Path.Combine("Test_EPF_MoveRenameFileTests", "ChangeUnhydratedFileName", "renamed_Program.cs"); // Don't read oldFilename or check for its existence before calling MoveFile, because doing so // can cause the file to hydrate @@ -86,7 +86,7 @@ public void ChangeNestedUnhydratedFileNameCase() { string oldName = "Program.cs"; string newName = "program.cs"; - string folderName = "Test_EPF_MoveRenameFileTests\\ChangeNestedUnhydratedFileNameCase\\"; + string folderName = Path.Combine("Test_EPF_MoveRenameFileTests", "ChangeNestedUnhydratedFileNameCase"); string oldVirtualPath = this.Enlistment.GetVirtualPathTo(Path.Combine(folderName, oldName)); string newVirtualPath = this.Enlistment.GetVirtualPathTo(Path.Combine(folderName, newName)); @@ -101,8 +101,8 @@ public void MoveUnhydratedFileToDotGitFolder() this.Enlistment.GetVirtualPathTo(targetFolderName).ShouldBeADirectory(this.fileSystem); string testFileName = "Program.cs"; - string testFileFolder = "Test_EPF_MoveRenameFileTests\\MoveUnhydratedFileToDotGitFolder"; - string testFilePathSubPath = testFileFolder + "\\" + testFileName; + string testFileFolder = Path.Combine("Test_EPF_MoveRenameFileTests", "MoveUnhydratedFileToDotGitFolder"); + string testFilePathSubPath = Path.Combine(testFileFolder, testFileName); string newTestFileVirtualPath = Path.Combine(this.Enlistment.GetVirtualPathTo(targetFolderName), testFileName); diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/MoveRenameFileTests_2.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/MoveRenameFileTests_2.cs index 01c2fe908..746f22561 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/MoveRenameFileTests_2.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/MoveRenameFileTests_2.cs @@ -8,7 +8,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture // TODO 452590 - Combine all of the MoveRenameTests into a single fixture, and have each use different // well known files [TestFixtureSource(typeof(FileSystemRunner), FileSystemRunner.TestRunners)] - [Category(Categories.Mac.M2TODO)] + [Category(Categories.Mac.M2)] public class MoveRenameFileTests_2 : TestsWithEnlistmentPerFixture { private const string TestFileFolder = "Test_EPF_MoveRenameFileTests_2"; @@ -44,8 +44,8 @@ public void MoveUnhydratedFileToUnhydratedFolderAndWrite() // Assume there will always be a GVFS folder when running tests string testFolderName = "GVFS"; - string oldTestFileVirtualPath = this.Enlistment.GetVirtualPathTo(TestFileFolder + "\\" + testFileName); - string newTestFileVirtualPath = this.Enlistment.GetVirtualPathTo(testFolderName + "\\" + testFileName); + string oldTestFileVirtualPath = this.Enlistment.GetVirtualPathTo(TestFileFolder, testFileName); + string newTestFileVirtualPath = this.Enlistment.GetVirtualPathTo(testFolderName, testFileName); this.fileSystem.MoveFile(oldTestFileVirtualPath, newTestFileVirtualPath); oldTestFileVirtualPath.ShouldNotExistOnDisk(this.fileSystem); @@ -72,8 +72,8 @@ public void MoveUnhydratedFileToNewFolderAndWrite() string newTestFileVirtualPath = Path.Combine(this.Enlistment.GetVirtualPathTo(testFolderName), testFolderName); - this.fileSystem.MoveFile(this.Enlistment.GetVirtualPathTo(TestFileFolder + "\\" + testFileName), newTestFileVirtualPath); - this.Enlistment.GetVirtualPathTo(TestFileFolder + "\\" + testFileName).ShouldNotExistOnDisk(this.fileSystem); + this.fileSystem.MoveFile(this.Enlistment.GetVirtualPathTo(TestFileFolder, testFileName), newTestFileVirtualPath); + this.Enlistment.GetVirtualPathTo(TestFileFolder, testFileName).ShouldNotExistOnDisk(this.fileSystem); newTestFileVirtualPath.ShouldBeAFile(this.fileSystem).WithContents(testFileContents); // Writing after the move should succeed @@ -92,8 +92,8 @@ public void MoveUnhydratedFileToNewFolderAndWrite() [TestCase, Order(3)] public void MoveUnhydratedFileToOverwriteUnhydratedFileAndWrite() { - string targetFilename = TestFileFolder + "\\MoveUnhydratedFileToOverwriteUnhydratedFileAndWrite\\RunFunctionalTests.bat"; - string sourceFilename = TestFileFolder + "\\MoveUnhydratedFileToOverwriteUnhydratedFileAndWrite\\RunUnitTests.bat"; + string targetFilename = Path.Combine(TestFileFolder, "MoveUnhydratedFileToOverwriteUnhydratedFileAndWrite", "RunFunctionalTests.bat"); + string sourceFilename = Path.Combine(TestFileFolder, "MoveUnhydratedFileToOverwriteUnhydratedFileAndWrite", "RunUnitTests.bat"); string sourceFileContents = RunUnitTestsContents; // Overwriting one unhydrated file with another should create a file at the target @@ -130,7 +130,11 @@ public void MoveUnhydratedFileToOverwriteFullFileAndWrite() string targetFilename = "TargetFile.txt"; string targetFileContents = "The Target"; - string sourceFilename = TestFileFolder + "\\MoveUnhydratedFileToOverwriteFullFileAndWrite\\MoveUnhydratedFileToOverwriteFullFileAndWrite.txt"; + string sourceFilename = Path.Combine( + TestFileFolder, + "MoveUnhydratedFileToOverwriteFullFileAndWrite", + "MoveUnhydratedFileToOverwriteFullFileAndWrite.txt"); + string sourceFileContents = @" diff --git a/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs b/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs index 66a7a83c9..2e54d0284 100644 --- a/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs +++ b/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs @@ -102,6 +102,7 @@ protected override bool TryStart(out string error) this.virtualizationInstance.OnFileModified = this.OnFileModified; this.virtualizationInstance.OnPreDelete = this.OnPreDelete; this.virtualizationInstance.OnNewFileCreated = this.OnNewFileCreated; + this.virtualizationInstance.OnFileRenamed = this.OnFileRenamed; uint threadCount = (uint)Environment.ProcessorCount * 2; @@ -118,8 +119,8 @@ protected override bool TryStart(out string error) this.Context.Tracer.RelatedEvent(EventLevel.Informational, $"{nameof(this.TryStart)}_StartedVirtualization", metadata: null); return true; - } - + } + private static byte[] ToVersionIdByteArray(byte[] version) { byte[] bytes = new byte[VirtualizationInstance.PlaceholderIdLength]; @@ -189,8 +190,8 @@ private Result OnGetFileStream( (stream, blobLength) => { // TODO(Mac): Find a better solution than reading from the stream one byte at at time - byte[] buffer = new byte[4096]; - uint bufferIndex = 0; + byte[] buffer = new byte[4096]; + uint bufferIndex = 0; int nextByte = stream.ReadByte(); while (nextByte != -1) { @@ -347,6 +348,16 @@ private void OnNewFileCreated(string relativePath, bool isDirectory) this.LogUnhandledExceptionAndExit(nameof(this.OnNewFileCreated), metadata); } } + + private void OnFileRenamed(string relativeDestinationPath, bool isDirectory) + { + // relativeSourcePath is handled in the OnPreDelete callback that's triggered + // prior to OnFileRenamed + this.OnFileRenamed( + relativeSourcePath: string.Empty, + relativeDestinationPath: relativeDestinationPath, + isDirectory: isDirectory); + } private Result OnEnumerateDirectory( ulong commandId, diff --git a/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs b/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs index 02167bd3e..6fb439171 100644 --- a/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs +++ b/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs @@ -1223,35 +1223,7 @@ private void NotifyFileRenamedHandler( bool isDirectory, ref NotificationType notificationMask) { - try - { - bool srcPathInDotGit = FileSystemCallbacks.IsPathInsideDotGit(virtualPath); - bool dstPathInDotGit = FileSystemCallbacks.IsPathInsideDotGit(destinationPath); - - if (dstPathInDotGit) - { - this.OnDotGitFileOrFolderChanged(destinationPath); - } - - if (!(srcPathInDotGit && dstPathInDotGit)) - { - if (isDirectory) - { - this.FileSystemCallbacks.OnFolderRenamed(virtualPath, destinationPath); - } - else - { - this.FileSystemCallbacks.OnFileRenamed(virtualPath, destinationPath); - } - } - } - catch (Exception e) - { - EventMetadata metadata = this.CreateEventMetadata(virtualPath, e); - metadata.Add("destinationPath", destinationPath); - metadata.Add("isDirectory", isDirectory); - this.LogUnhandledExceptionAndExit(nameof(this.NotifyFileRenamedHandler), metadata); - } + this.OnFileRenamed(virtualPath, destinationPath, isDirectory); } private void NotifyFileHandleClosedFileModifiedOrDeletedHandler( diff --git a/GVFS/GVFS.Virtualization/FileSystem/FileSystemVirtualizer.cs b/GVFS/GVFS.Virtualization/FileSystem/FileSystemVirtualizer.cs index 33c90c988..acd21633b 100644 --- a/GVFS/GVFS.Virtualization/FileSystem/FileSystemVirtualizer.cs +++ b/GVFS/GVFS.Virtualization/FileSystem/FileSystemVirtualizer.cs @@ -208,6 +208,39 @@ protected void OnWorkingDirectoryFileOrFolderDeleted(string relativePath, bool i this.FileSystemCallbacks.InvalidateGitStatusCache(); } + + protected void OnFileRenamed(string relativeSourcePath, string relativeDestinationPath, bool isDirectory) + { + try + { + bool srcPathInDotGit = FileSystemCallbacks.IsPathInsideDotGit(relativeSourcePath); + bool dstPathInDotGit = FileSystemCallbacks.IsPathInsideDotGit(relativeDestinationPath); + + if (dstPathInDotGit) + { + this.OnDotGitFileOrFolderChanged(relativeDestinationPath); + } + + if (!(srcPathInDotGit && dstPathInDotGit)) + { + if (isDirectory) + { + this.FileSystemCallbacks.OnFolderRenamed(relativeSourcePath, relativeDestinationPath); + } + else + { + this.FileSystemCallbacks.OnFileRenamed(relativeSourcePath, relativeDestinationPath); + } + } + } + catch (Exception e) + { + EventMetadata metadata = this.CreateEventMetadata(relativeSourcePath, e); + metadata.Add("destinationPath", relativeDestinationPath); + metadata.Add("isDirectory", isDirectory); + this.LogUnhandledExceptionAndExit(nameof(this.OnFileRenamed), metadata); + } + } protected EventMetadata CreateEventMetadata( Guid enumerationId, diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp index 7dc73f78f..1f9e4b8d6 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp @@ -226,6 +226,8 @@ void KauthHandler_HandleKernelMessageResponse(uint64_t messageId, MessageType re case MessageType_KtoU_NotifyFilePreDelete: case MessageType_KtoU_NotifyDirectoryPreDelete: case MessageType_KtoU_NotifyFileCreated: + case MessageType_KtoU_NotifyFileRenamed: + case MessageType_KtoU_NotifyDirectoryRenamed: KextLog_Error("KauthHandler_HandleKernelMessageResponse: Unexpected responseType: %d", responseType); break; } @@ -376,8 +378,61 @@ static int HandleFileOpOperation( atomic_fetch_add(&s_numActiveKauthEvents, 1); vfs_context_t context = vfs_context_create(NULL); + vnode_t currentVnodeFromPath = NULLVP; - if (KAUTH_FILEOP_CLOSE == action) + if (KAUTH_FILEOP_RENAME == action) + { + // arg0 is the (const char *) fromPath + const char* toPath = (const char*)arg1; + + // TODO(Mac): Improve error handling + errno_t toErr = vnode_lookup(toPath, 0 /* flags */, ¤tVnodeFromPath, context); + if (0 != toErr) + { + goto CleanupAndReturn; + } + + vtype vnodeType = vnode_vtype(currentVnodeFromPath); + if (ShouldIgnoreVnodeType(vnodeType, currentVnodeFromPath)) + { + goto CleanupAndReturn; + } + + if (!HasAncestorFlaggedAsInRoot(currentVnodeFromPath, context)) + { + goto CleanupAndReturn; + } + + VirtualizationRoot* root = nullptr; + int pid; + if (!ShouldHandleFileOpEvent( + context, + currentVnodeFromPath, + action, + &root, + &pid)) + { + goto CleanupAndReturn; + } + + char procname[MAXCOMLEN + 1]; + proc_name(pid, procname, MAXCOMLEN + 1); + + int kauthResult; + int kauthError; + if (!TrySendRequestAndWaitForResponse( + root, + vnode_isdir(currentVnodeFromPath) ? MessageType_KtoU_NotifyDirectoryRenamed : MessageType_KtoU_NotifyFileRenamed, + currentVnodeFromPath, + pid, + procname, + &kauthResult, + &kauthError)) + { + goto CleanupAndReturn; + } + } + else if (KAUTH_FILEOP_CLOSE == action) { vnode_t currentVnode = reinterpret_cast(arg0); // arg1 is the (const char *) path @@ -449,7 +504,12 @@ static int HandleFileOpOperation( } } -CleanupAndReturn: +CleanupAndReturn: + if (NULLVP != currentVnodeFromPath) + { + vnode_put(currentVnodeFromPath); + } + vfs_context_rele(context); atomic_fetch_sub(&s_numActiveKauthEvents, 1); @@ -472,6 +532,7 @@ static bool ShouldHandleVnodeOpEvent( char procname[MAXCOMLEN + 1], int* kauthResult) { + *root = nullptr; *kauthResult = KAUTH_RESULT_DEFER; if (!VirtualizationRoot_VnodeIsOnAllowedFilesystem(vnode)) diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.hpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.hpp index a74d33e74..49389c10c 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.hpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.hpp @@ -18,7 +18,7 @@ struct VirtualizationRoot fsid_t rootFsid; uint64_t rootInode; - // TODO: this should eventually be entirely diagnostic and not used for decisions + // TODO(Mac): this should eventually be entirely diagnostic and not used for decisions char path[PrjFSMaxPath]; int32_t index; diff --git a/ProjFS.Mac/PrjFSKext/public/Message.h b/ProjFS.Mac/PrjFSKext/public/Message.h index 723303327..ffedf4175 100644 --- a/ProjFS.Mac/PrjFSKext/public/Message.h +++ b/ProjFS.Mac/PrjFSKext/public/Message.h @@ -20,6 +20,8 @@ typedef enum MessageType_KtoU_NotifyFilePreDelete, MessageType_KtoU_NotifyDirectoryPreDelete, MessageType_KtoU_NotifyFileCreated, + MessageType_KtoU_NotifyFileRenamed, + MessageType_KtoU_NotifyDirectoryRenamed, // Responses MessageType_Response_Success, diff --git a/ProjFS.Mac/PrjFSLib.Mac.Managed/CallbackDelegates.cs b/ProjFS.Mac/PrjFSLib.Mac.Managed/CallbackDelegates.cs index bb70f658e..26254b2dc 100644 --- a/ProjFS.Mac/PrjFSLib.Mac.Managed/CallbackDelegates.cs +++ b/ProjFS.Mac/PrjFSLib.Mac.Managed/CallbackDelegates.cs @@ -31,8 +31,7 @@ public delegate Result NotifyOperationCallback( int triggeringProcessId, string triggeringProcessName, bool isDirectory, - NotificationType notificationType, - string destinationRelativePath); + NotificationType notificationType); // Pre-event notifications public delegate Result NotifyPreDeleteEvent( @@ -51,7 +50,6 @@ public delegate void NotifyNewFileCreatedEvent( bool isDirectory); public delegate void NotifyFileRenamedEvent( - string relativeSourcePath, string relativeDestinationPath, bool isDirectory); diff --git a/ProjFS.Mac/PrjFSLib.Mac.Managed/VirtualizationInstance.cs b/ProjFS.Mac/PrjFSLib.Mac.Managed/VirtualizationInstance.cs index 40638e54b..587822842 100644 --- a/ProjFS.Mac/PrjFSLib.Mac.Managed/VirtualizationInstance.cs +++ b/ProjFS.Mac/PrjFSLib.Mac.Managed/VirtualizationInstance.cs @@ -16,8 +16,9 @@ public class VirtualizationInstance public virtual NotifyFileModified OnFileModified { get; set; } public virtual NotifyPreDeleteEvent OnPreDelete { get; set; } - public virtual NotifyNewFileCreatedEvent OnNewFileCreated { get; set; } - + public virtual NotifyNewFileCreatedEvent OnNewFileCreated { get; set; } + public virtual NotifyFileRenamedEvent OnFileRenamed { get; set; } + public static Result ConvertDirectoryToVirtualizationRoot(string fullPath) { return Interop.PrjFSLib.ConvertDirectoryToVirtualizationRoot(fullPath); @@ -131,8 +132,7 @@ private Result OnNotifyOperation( int triggeringProcessId, string triggeringProcessName, bool isDirectory, - NotificationType notificationType, - string destinationRelativePath) + NotificationType notificationType) { switch (notificationType) { @@ -146,6 +146,10 @@ private Result OnNotifyOperation( case NotificationType.NewFileCreated: this.OnNewFileCreated(relativePath, isDirectory); return Result.Success; + + case NotificationType.FileRenamed: + this.OnFileRenamed(relativePath, isDirectory); + return Result.Success; } return Result.ENotYetImplemented; diff --git a/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp b/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp index 5e18ffec9..5312f0d1f 100644 --- a/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp +++ b/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp @@ -432,38 +432,44 @@ static void HandleKernelRequest(Message request, void* messageMemory) } case MessageType_KtoU_NotifyFilePreDelete: + case MessageType_KtoU_NotifyDirectoryPreDelete: { result = HandleFileNotification( requestHeader, request.path, - false, // isDirectory + requestHeader->messageType == MessageType_KtoU_NotifyDirectoryPreDelete, // isDirectory PrjFS_NotificationType_PreDelete); break; } - case MessageType_KtoU_NotifyDirectoryPreDelete: + case MessageType_KtoU_NotifyFileCreated: { + char fullPath[PrjFSMaxPath]; + CombinePaths(s_virtualizationRootFullPath.c_str(), request.path, fullPath); + SetBitInFileFlags(fullPath, FileFlags_IsInVirtualizationRoot, true); + result = HandleFileNotification( requestHeader, request.path, - true, // isDirectory - PrjFS_NotificationType_PreDelete); + false, // isDirectory + PrjFS_NotificationType_NewFileCreated); break; } - case MessageType_KtoU_NotifyFileCreated: + case MessageType_KtoU_NotifyFileRenamed: + case MessageType_KtoU_NotifyDirectoryRenamed: { char fullPath[PrjFSMaxPath]; CombinePaths(s_virtualizationRootFullPath.c_str(), request.path, fullPath); // TODO(Mac): Handle SetBitInFileFlags failures SetBitInFileFlags(fullPath, FileFlags_IsInVirtualizationRoot, true); - + result = HandleFileNotification( requestHeader, request.path, - false, // isDirectory - PrjFS_NotificationType_NewFileCreated); + requestHeader->messageType == MessageType_KtoU_NotifyDirectoryRenamed, // isDirectory + PrjFS_NotificationType_FileRenamed); break; } } From d3e24b31a61cdc0cb73d675e684e627430acdc0b Mon Sep 17 00:00:00 2001 From: William Baker Date: Mon, 20 Aug 2018 15:04:26 -0700 Subject: [PATCH 02/10] Update MirrorProvider for rename notification --- .../MirrorProvider.Mac/MacFileSystemVirtualizer.cs | 8 +++++++- .../WindowsFileSystemVirtualizer.cs | 13 ++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/MirrorProvider/MirrorProvider.Mac/MacFileSystemVirtualizer.cs b/MirrorProvider/MirrorProvider.Mac/MacFileSystemVirtualizer.cs index 5f20521bb..5bd11e6e9 100644 --- a/MirrorProvider/MirrorProvider.Mac/MacFileSystemVirtualizer.cs +++ b/MirrorProvider/MirrorProvider.Mac/MacFileSystemVirtualizer.cs @@ -22,7 +22,8 @@ public override bool TryStartVirtualizationInstance(Enlistment enlistment, out s this.virtualizationInstance.OnGetFileStream = this.OnGetFileStream; this.virtualizationInstance.OnFileModified = this.OnFileModified; this.virtualizationInstance.OnPreDelete = this.OnPreDelete; - this.virtualizationInstance.OnNewFileCreated = this.OnNewFileCreated; + this.virtualizationInstance.OnNewFileCreated = this.OnNewFileCreated; + this.virtualizationInstance.OnFileRenamed = this.OnFileRenamed; Result result = this.virtualizationInstance.StartVirtualizationInstance( enlistment.SrcRoot, @@ -164,6 +165,11 @@ private void OnNewFileCreated(string relativePath, bool isDirectory) { Console.WriteLine($"OnNewFileCreated (isDirectory: {isDirectory}): {relativePath}"); } + + private void OnFileRenamed(string relativeDestinationPath, bool isDirectory) + { + Console.WriteLine($"OnFileRenamed (isDirectory: {isDirectory}) destination: {relativeDestinationPath}"); + } private static byte[] ToVersionIdByteArray(byte version) { diff --git a/MirrorProvider/MirrorProvider.Windows/WindowsFileSystemVirtualizer.cs b/MirrorProvider/MirrorProvider.Windows/WindowsFileSystemVirtualizer.cs index cecea9dce..b78cc2391 100644 --- a/MirrorProvider/MirrorProvider.Windows/WindowsFileSystemVirtualizer.cs +++ b/MirrorProvider/MirrorProvider.Windows/WindowsFileSystemVirtualizer.cs @@ -37,6 +37,7 @@ public override bool TryStartVirtualizationInstance(Enlistment enlistment, out s this.virtualizationInstance.OnNotifyPreDelete = this.OnPreDelete; this.virtualizationInstance.OnNotifyNewFileCreated = this.OnNewFileCreated; this.virtualizationInstance.OnNotifyFileHandleClosedFileModifiedOrDeleted = this.OnFileModifiedOrDeleted; + this.virtualizationInstance.OnNotifyFileRenamed = this.OnFileRenamed; uint threadCount = (uint)Environment.ProcessorCount * 2; @@ -44,7 +45,8 @@ public override bool TryStartVirtualizationInstance(Enlistment enlistment, out s { new NotificationMapping( NotificationType.NewFileCreated | - NotificationType.PreDelete | + NotificationType.PreDelete | + NotificationType.FileRenamed | NotificationType.FileHandleClosedFileModified, string.Empty), }; @@ -316,6 +318,15 @@ private void OnFileModifiedOrDeleted(string relativePath, bool isDirectory, bool Console.WriteLine($"OnFileModifiedOrDeleted: `{relativePath}`, isDirectory: {isDirectory}, isModfied: {isFileDeleted}, isDeleted: {isFileDeleted}"); } + private void OnFileRenamed( + string relativePath, + string relativeDestinationPath, + bool isDirectory, + ref NotificationType notificationMask) + { + Console.WriteLine($"OnFileRenamed (isDirectory: {isDirectory}), relativePath: {relativePath}, relativeDestinationPath: {relativeDestinationPath}"); + } + // TODO: Add this to the ProjFS API private static HResult HResultFromWin32(int win32error) { From 1918f53b1b6b9329d6e18258b0423f744235636c Mon Sep 17 00:00:00 2001 From: William Baker Date: Tue, 21 Aug 2018 09:09:26 -0700 Subject: [PATCH 03/10] Mac: Hardlink created notification --- .../EnlistmentPerFixture/GitFilesTests.cs | 2 -- .../GitMoveRenameTests.cs | 13 ----------- .../MacFileSystemVirtualizer.cs | 23 +++++++++++++++++++ .../FileSystemCallbacksTests.cs | 6 +++++ .../Background/FileSystemTask.cs | 8 ++++++- .../FileSystemCallbacks.cs | 6 +++++ .../PrjFSKext/PrjFSKext/KauthHandler.cpp | 22 ++++++++++++++---- ProjFS.Mac/PrjFSKext/public/Message.h | 1 + .../PrjFSLib.Mac.Managed/CallbackDelegates.cs | 3 +++ .../PrjFSLib.Mac.Managed/NotificationType.cs | 1 + .../VirtualizationInstance.cs | 5 ++++ ProjFS.Mac/PrjFSLib/PrjFSLib.cpp | 18 +++++++++++++-- ProjFS.Mac/PrjFSLib/PrjFSLib.h | 1 + 13 files changed, 86 insertions(+), 23 deletions(-) diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs index 1f8d91d38..7abf164af 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs @@ -189,8 +189,6 @@ public void ModifiedFileWillGetAddedToModifiedPathsFile() [TestCase, Order(8)] public void RenamedFileAddedToModifiedPathsFile() { - GitMoveRenameTests.IgnoreSystemIOMoveOnMac(this.fileSystem); - string fileToRenameEntry = "Test_EPF_MoveRenameFileTests/ChangeUnhydratedFileName/Program.cs"; string fileToRenameTargetEntry = "Test_EPF_MoveRenameFileTests/ChangeUnhydratedFileName/Program2.cs"; this.VerifyWorktreeBit(fileToRenameEntry, LsFilesStatus.SkipWorktree); diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitMoveRenameTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitMoveRenameTests.cs index 30f1e4ae3..fa0fbe74f 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitMoveRenameTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitMoveRenameTests.cs @@ -22,15 +22,6 @@ public GitMoveRenameTests(FileSystemRunner fileSystem) this.fileSystem = fileSystem; } - public static void IgnoreSystemIOMoveOnMac(FileSystemRunner runner) - { - if (runner is SystemIORunner && - RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - Assert.Ignore("TODO(Mac): SystemIORunner rename tests require hardlink notifications"); - } - } - [TestCase, Order(1)] public void GitStatus() { @@ -85,8 +76,6 @@ public void GitStatusAfterFileNameCaseChange() [TestCase, Order(4)] public void GitStatusAfterFileRename() { - IgnoreSystemIOMoveOnMac(this.fileSystem); - string oldFilename = "New.cs"; this.EnsureTestFileExists(oldFilename); @@ -195,8 +184,6 @@ public void GitWithEnvironmentVariables() [TestCase, Order(9)] public void GitStatusAfterRenameFileIntoRepo() { - IgnoreSystemIOMoveOnMac(this.fileSystem); - string filename = "GitStatusAfterRenameFileIntoRepo.cs"; // Create the test file in this.Enlistment.EnlistmentRoot as it's outside of src diff --git a/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs b/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs index 2e54d0284..081eef322 100644 --- a/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs +++ b/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs @@ -103,6 +103,7 @@ protected override bool TryStart(out string error) this.virtualizationInstance.OnPreDelete = this.OnPreDelete; this.virtualizationInstance.OnNewFileCreated = this.OnNewFileCreated; this.virtualizationInstance.OnFileRenamed = this.OnFileRenamed; + this.virtualizationInstance.OnHardLinkCreated = this.OnHardLinkCreated; uint threadCount = (uint)Environment.ProcessorCount * 2; @@ -359,6 +360,28 @@ private void OnFileRenamed(string relativeDestinationPath, bool isDirectory) isDirectory: isDirectory); } + private void OnHardLinkCreated(string relativeNewLinkPath) + { + try + { + bool pathInDotGit = Virtualization.FileSystemCallbacks.IsPathInsideDotGit(relativeNewLinkPath); + + if (pathInDotGit) + { + this.OnDotGitFileOrFolderChanged(relativeNewLinkPath); + } + else + { + this.FileSystemCallbacks.OnFileHardLinkCreated(relativeNewLinkPath); + } + } + catch (Exception e) + { + EventMetadata metadata = this.CreateEventMetadata(relativeNewLinkPath, e); + this.LogUnhandledExceptionAndExit(nameof(this.OnHardLinkCreated), metadata); + } + } + private Result OnEnumerateDirectory( ulong commandId, string relativePath, diff --git a/GVFS/GVFS.UnitTests/Virtualization/FileSystemCallbacksTests.cs b/GVFS/GVFS.UnitTests/Virtualization/FileSystemCallbacksTests.cs index 55c1ad296..7585b6179 100644 --- a/GVFS/GVFS.UnitTests/Virtualization/FileSystemCallbacksTests.cs +++ b/GVFS/GVFS.UnitTests/Virtualization/FileSystemCallbacksTests.cs @@ -275,6 +275,12 @@ public void FileAndFolderCallbacksScheduleBackgroundTasks() "OnFileRenamed2.txt", FileSystemTask.OperationType.OnFileRenamed); + this.CallbackSchedulesBackgroundTask( + backgroundTaskRunner, + (path) => fileSystemCallbacks.OnFileHardLinkCreated(path), + "OnFileHardLinkCreated.txt", + FileSystemTask.OperationType.OnFileHardLinkCreated); + this.CallbackSchedulesBackgroundTask( backgroundTaskRunner, (path) => fileSystemCallbacks.OnFileSuperseded(path), diff --git a/GVFS/GVFS.Virtualization/Background/FileSystemTask.cs b/GVFS/GVFS.Virtualization/Background/FileSystemTask.cs index a88d18e6a..b5644bf86 100644 --- a/GVFS/GVFS.Virtualization/Background/FileSystemTask.cs +++ b/GVFS/GVFS.Virtualization/Background/FileSystemTask.cs @@ -28,7 +28,8 @@ public enum OperationType OnFolderDeleted, OnFolderFirstWrite, OnIndexWriteWithoutProjectionChange, - OnPlaceholderCreationsBlockedForGit + OnPlaceholderCreationsBlockedForGit, + OnFileHardLinkCreated } public OperationType Operation { get; } @@ -46,6 +47,11 @@ public static FileSystemTask OnFileRenamed(string oldVirtualPath, string newVirt return new FileSystemTask(OperationType.OnFileRenamed, newVirtualPath, oldVirtualPath); } + public static FileSystemTask OnFileHardLinkCreated(string newLinkRelativePath) + { + return new FileSystemTask(OperationType.OnFileHardLinkCreated, newLinkRelativePath, oldVirtualPath: null); + } + public static FileSystemTask OnFileDeleted(string virtualPath) { return new FileSystemTask(OperationType.OnFileDeleted, virtualPath, oldVirtualPath: null); diff --git a/GVFS/GVFS.Virtualization/FileSystemCallbacks.cs b/GVFS/GVFS.Virtualization/FileSystemCallbacks.cs index 906bad018..980ab2b87 100644 --- a/GVFS/GVFS.Virtualization/FileSystemCallbacks.cs +++ b/GVFS/GVFS.Virtualization/FileSystemCallbacks.cs @@ -423,6 +423,11 @@ public virtual void OnFileRenamed(string oldRelativePath, string newRelativePath this.backgroundFileSystemTaskRunner.Enqueue(FileSystemTask.OnFileRenamed(oldRelativePath, newRelativePath)); } + public virtual void OnFileHardLinkCreated(string newLinkRelativePath) + { + this.backgroundFileSystemTaskRunner.Enqueue(FileSystemTask.OnFileHardLinkCreated(newLinkRelativePath)); + } + public void OnFileDeleted(string relativePath) { this.backgroundFileSystemTaskRunner.Enqueue(FileSystemTask.OnFileDeleted(relativePath)); @@ -606,6 +611,7 @@ private FileSystemTaskResult ExecuteBackgroundOperation(FileSystemTask gitUpdate { case FileSystemTask.OperationType.OnFileCreated: case FileSystemTask.OperationType.OnFailedPlaceholderDelete: + case FileSystemTask.OperationType.OnFileHardLinkCreated: metadata.Add("virtualPath", gitUpdate.VirtualPath); result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath); break; diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp index 1f9e4b8d6..f7f9fc6ff 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp @@ -228,6 +228,7 @@ void KauthHandler_HandleKernelMessageResponse(uint64_t messageId, MessageType re case MessageType_KtoU_NotifyFileCreated: case MessageType_KtoU_NotifyFileRenamed: case MessageType_KtoU_NotifyDirectoryRenamed: + case MessageType_KtoU_NotifyFileHardLinkCreated: KextLog_Error("KauthHandler_HandleKernelMessageResponse: Unexpected responseType: %d", responseType); break; } @@ -380,13 +381,14 @@ static int HandleFileOpOperation( vfs_context_t context = vfs_context_create(NULL); vnode_t currentVnodeFromPath = NULLVP; - if (KAUTH_FILEOP_RENAME == action) + if (KAUTH_FILEOP_RENAME == action || + KAUTH_FILEOP_LINK == action) { - // arg0 is the (const char *) fromPath - const char* toPath = (const char*)arg1; + // arg0 is the (const char *) fromPath (or the file being linked to) + const char* newPath = (const char*)arg1; // TODO(Mac): Improve error handling - errno_t toErr = vnode_lookup(toPath, 0 /* flags */, ¤tVnodeFromPath, context); + errno_t toErr = vnode_lookup(newPath, 0 /* flags */, ¤tVnodeFromPath, context); if (0 != toErr) { goto CleanupAndReturn; @@ -418,11 +420,21 @@ static int HandleFileOpOperation( char procname[MAXCOMLEN + 1]; proc_name(pid, procname, MAXCOMLEN + 1); + MessageType messageType; + if (KAUTH_FILEOP_RENAME == action) + { + messageType = vnode_isdir(currentVnodeFromPath) ? MessageType_KtoU_NotifyDirectoryRenamed : MessageType_KtoU_NotifyFileRenamed; + } + else + { + messageType = MessageType_KtoU_NotifyFileHardLinkCreated; + } + int kauthResult; int kauthError; if (!TrySendRequestAndWaitForResponse( root, - vnode_isdir(currentVnodeFromPath) ? MessageType_KtoU_NotifyDirectoryRenamed : MessageType_KtoU_NotifyFileRenamed, + messageType, currentVnodeFromPath, pid, procname, diff --git a/ProjFS.Mac/PrjFSKext/public/Message.h b/ProjFS.Mac/PrjFSKext/public/Message.h index ffedf4175..a4a200fb8 100644 --- a/ProjFS.Mac/PrjFSKext/public/Message.h +++ b/ProjFS.Mac/PrjFSKext/public/Message.h @@ -22,6 +22,7 @@ typedef enum MessageType_KtoU_NotifyFileCreated, MessageType_KtoU_NotifyFileRenamed, MessageType_KtoU_NotifyDirectoryRenamed, + MessageType_KtoU_NotifyFileHardLinkCreated, // Responses MessageType_Response_Success, diff --git a/ProjFS.Mac/PrjFSLib.Mac.Managed/CallbackDelegates.cs b/ProjFS.Mac/PrjFSLib.Mac.Managed/CallbackDelegates.cs index 26254b2dc..769a9743e 100644 --- a/ProjFS.Mac/PrjFSLib.Mac.Managed/CallbackDelegates.cs +++ b/ProjFS.Mac/PrjFSLib.Mac.Managed/CallbackDelegates.cs @@ -53,6 +53,9 @@ public delegate void NotifyFileRenamedEvent( string relativeDestinationPath, bool isDirectory); + public delegate void NotifyHardLinkCreatedEvent( + string relativeNewLinkPath); + public delegate void NotifyFileModified( string relativePath); diff --git a/ProjFS.Mac/PrjFSLib.Mac.Managed/NotificationType.cs b/ProjFS.Mac/PrjFSLib.Mac.Managed/NotificationType.cs index 7e3c6b084..08b780d72 100644 --- a/ProjFS.Mac/PrjFSLib.Mac.Managed/NotificationType.cs +++ b/ProjFS.Mac/PrjFSLib.Mac.Managed/NotificationType.cs @@ -11,6 +11,7 @@ public enum NotificationType NewFileCreated = 0x00000004, PreDelete = 0x00000010, FileRenamed = 0x00000080, + HardLinkCreated = 0x00000100, PreConvertToFull = 0x00001000, PreModify = 0x10000001, diff --git a/ProjFS.Mac/PrjFSLib.Mac.Managed/VirtualizationInstance.cs b/ProjFS.Mac/PrjFSLib.Mac.Managed/VirtualizationInstance.cs index 587822842..b2ddce178 100644 --- a/ProjFS.Mac/PrjFSLib.Mac.Managed/VirtualizationInstance.cs +++ b/ProjFS.Mac/PrjFSLib.Mac.Managed/VirtualizationInstance.cs @@ -18,6 +18,7 @@ public class VirtualizationInstance public virtual NotifyPreDeleteEvent OnPreDelete { get; set; } public virtual NotifyNewFileCreatedEvent OnNewFileCreated { get; set; } public virtual NotifyFileRenamedEvent OnFileRenamed { get; set; } + public virtual NotifyHardLinkCreatedEvent OnHardLinkCreated { get; set; } public static Result ConvertDirectoryToVirtualizationRoot(string fullPath) { @@ -150,6 +151,10 @@ private Result OnNotifyOperation( case NotificationType.FileRenamed: this.OnFileRenamed(relativePath, isDirectory); return Result.Success; + + case NotificationType.HardLinkCreated: + this.OnHardLinkCreated(relativePath); + return Result.Success; } return Result.ENotYetImplemented; diff --git a/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp b/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp index 5312f0d1f..36be88ef3 100644 --- a/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp +++ b/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp @@ -458,6 +458,7 @@ static void HandleKernelRequest(Message request, void* messageMemory) case MessageType_KtoU_NotifyFileRenamed: case MessageType_KtoU_NotifyDirectoryRenamed: + case MessageType_KtoU_NotifyFileHardLinkCreated: { char fullPath[PrjFSMaxPath]; CombinePaths(s_virtualizationRootFullPath.c_str(), request.path, fullPath); @@ -465,11 +466,22 @@ static void HandleKernelRequest(Message request, void* messageMemory) // TODO(Mac): Handle SetBitInFileFlags failures SetBitInFileFlags(fullPath, FileFlags_IsInVirtualizationRoot, true); + bool isDirectory = requestHeader->messageType == MessageType_KtoU_NotifyDirectoryRenamed; + PrjFS_NotificationType notificationType; + if (requestHeader->messageType == MessageType_KtoU_NotifyFileHardLinkCreated) + { + notificationType = PrjFS_NotificationType_HardLinkCreated; + } + else + { + notificationType = PrjFS_NotificationType_FileRenamed; + } + result = HandleFileNotification( requestHeader, request.path, - requestHeader->messageType == MessageType_KtoU_NotifyDirectoryRenamed, // isDirectory - PrjFS_NotificationType_FileRenamed); + isDirectory, + notificationType); break; } } @@ -783,6 +795,8 @@ static const char* NotificationTypeToString(PrjFS_NotificationType notificationT return STRINGIFY(PrjFS_NotificationType_PreDelete); case PrjFS_NotificationType_FileRenamed: return STRINGIFY(PrjFS_NotificationType_FileRenamed); + case PrjFS_NotificationType_HardLinkCreated: + return STRINGIFY(PrjFS_NotificationType_HardLinkCreated); case PrjFS_NotificationType_PreConvertToFull: return STRINGIFY(PrjFS_NotificationType_PreConvertToFull); diff --git a/ProjFS.Mac/PrjFSLib/PrjFSLib.h b/ProjFS.Mac/PrjFSLib/PrjFSLib.h index 98b512dc0..ceddbb578 100644 --- a/ProjFS.Mac/PrjFSLib/PrjFSLib.h +++ b/ProjFS.Mac/PrjFSLib/PrjFSLib.h @@ -46,6 +46,7 @@ typedef enum PrjFS_NotificationType_NewFileCreated = 0x00000004, PrjFS_NotificationType_PreDelete = 0x00000010, PrjFS_NotificationType_FileRenamed = 0x00000080, + PrjFS_NotificationType_HardLinkCreated = 0x00000100, PrjFS_NotificationType_PreConvertToFull = 0x00001000, PrjFS_NotificationType_PreModify = 0x10000001, From 690c1d484f5e2f0b345f5bc6bc2f0cd89124e267 Mon Sep 17 00:00:00 2001 From: William Baker Date: Tue, 21 Aug 2018 09:53:53 -0700 Subject: [PATCH 04/10] Update MirrorProvider for hardlink notification --- .../MirrorProvider.Mac/MacFileSystemVirtualizer.cs | 8 +++++++- .../WindowsFileSystemVirtualizer.cs | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/MirrorProvider/MirrorProvider.Mac/MacFileSystemVirtualizer.cs b/MirrorProvider/MirrorProvider.Mac/MacFileSystemVirtualizer.cs index 5bd11e6e9..3d9396c82 100644 --- a/MirrorProvider/MirrorProvider.Mac/MacFileSystemVirtualizer.cs +++ b/MirrorProvider/MirrorProvider.Mac/MacFileSystemVirtualizer.cs @@ -23,7 +23,8 @@ public override bool TryStartVirtualizationInstance(Enlistment enlistment, out s this.virtualizationInstance.OnFileModified = this.OnFileModified; this.virtualizationInstance.OnPreDelete = this.OnPreDelete; this.virtualizationInstance.OnNewFileCreated = this.OnNewFileCreated; - this.virtualizationInstance.OnFileRenamed = this.OnFileRenamed; + this.virtualizationInstance.OnFileRenamed = this.OnFileRenamed; + this.virtualizationInstance.OnHardLinkCreated = this.OnHardLinkCreated; Result result = this.virtualizationInstance.StartVirtualizationInstance( enlistment.SrcRoot, @@ -170,6 +171,11 @@ private void OnFileRenamed(string relativeDestinationPath, bool isDirectory) { Console.WriteLine($"OnFileRenamed (isDirectory: {isDirectory}) destination: {relativeDestinationPath}"); } + + private void OnHardLinkCreated(string relativeNewLinkPath) + { + Console.WriteLine($"OnHardLinkCreated: {relativeNewLinkPath}"); + } private static byte[] ToVersionIdByteArray(byte version) { diff --git a/MirrorProvider/MirrorProvider.Windows/WindowsFileSystemVirtualizer.cs b/MirrorProvider/MirrorProvider.Windows/WindowsFileSystemVirtualizer.cs index b78cc2391..edfd0acf1 100644 --- a/MirrorProvider/MirrorProvider.Windows/WindowsFileSystemVirtualizer.cs +++ b/MirrorProvider/MirrorProvider.Windows/WindowsFileSystemVirtualizer.cs @@ -38,6 +38,7 @@ public override bool TryStartVirtualizationInstance(Enlistment enlistment, out s this.virtualizationInstance.OnNotifyNewFileCreated = this.OnNewFileCreated; this.virtualizationInstance.OnNotifyFileHandleClosedFileModifiedOrDeleted = this.OnFileModifiedOrDeleted; this.virtualizationInstance.OnNotifyFileRenamed = this.OnFileRenamed; + this.virtualizationInstance.OnNotifyHardlinkCreated = this.OnHardlinkCreated; uint threadCount = (uint)Environment.ProcessorCount * 2; @@ -47,6 +48,7 @@ public override bool TryStartVirtualizationInstance(Enlistment enlistment, out s NotificationType.NewFileCreated | NotificationType.PreDelete | NotificationType.FileRenamed | + NotificationType.HardlinkCreated | NotificationType.FileHandleClosedFileModified, string.Empty), }; @@ -327,6 +329,13 @@ private void OnFileRenamed( Console.WriteLine($"OnFileRenamed (isDirectory: {isDirectory}), relativePath: {relativePath}, relativeDestinationPath: {relativeDestinationPath}"); } + private void OnHardlinkCreated( + string relativePath, + string relativeDestinationPath) + { + Console.WriteLine($"OnHardlinkCreated, relativePath: {relativePath}, relativeDestinationPath: {relativeDestinationPath}"); + } + // TODO: Add this to the ProjFS API private static HResult HResultFromWin32(int win32error) { From c144821f42996126bd39f9249e90bd43e4287170 Mon Sep 17 00:00:00 2001 From: William Baker Date: Tue, 21 Aug 2018 10:22:14 -0700 Subject: [PATCH 05/10] Update Windows code to handle hardlink creation --- .../MacFileSystemVirtualizer.cs | 21 +++-------------- .../WindowsFileSystemVirtualizer.cs | 9 +++++++- .../FileSystem/FileSystemVirtualizer.cs | 23 +++++++++++++++++++ 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs b/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs index 081eef322..330dad5ad 100644 --- a/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs +++ b/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs @@ -362,24 +362,9 @@ private void OnFileRenamed(string relativeDestinationPath, bool isDirectory) private void OnHardLinkCreated(string relativeNewLinkPath) { - try - { - bool pathInDotGit = Virtualization.FileSystemCallbacks.IsPathInsideDotGit(relativeNewLinkPath); - - if (pathInDotGit) - { - this.OnDotGitFileOrFolderChanged(relativeNewLinkPath); - } - else - { - this.FileSystemCallbacks.OnFileHardLinkCreated(relativeNewLinkPath); - } - } - catch (Exception e) - { - EventMetadata metadata = this.CreateEventMetadata(relativeNewLinkPath, e); - this.LogUnhandledExceptionAndExit(nameof(this.OnHardLinkCreated), metadata); - } + this.OnHardLinkCreated( + relativeTargetPath: string.Empty, + relativeNewLinkPath: relativeNewLinkPath); } private Result OnEnumerateDirectory( diff --git a/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs b/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs index 6fb439171..65f5d67ad 100644 --- a/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs +++ b/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs @@ -159,7 +159,7 @@ protected override bool TryStart(out string error) this.virtualizationInstance.OnNotifyPreRename = this.NotifyPreRenameHandler; this.virtualizationInstance.OnNotifyPreSetHardlink = null; this.virtualizationInstance.OnNotifyFileRenamed = this.NotifyFileRenamedHandler; - this.virtualizationInstance.OnNotifyHardlinkCreated = null; + this.virtualizationInstance.OnNotifyHardlinkCreated = this.NotifyHardlinkCreated; this.virtualizationInstance.OnNotifyFileHandleClosedNoModification = null; this.virtualizationInstance.OnNotifyFileHandleClosedFileModifiedOrDeleted = this.NotifyFileHandleClosedFileModifiedOrDeletedHandler; this.virtualizationInstance.OnNotifyFilePreConvertToFull = this.NotifyFilePreConvertToFullHandler; @@ -1226,6 +1226,13 @@ private void NotifyFileRenamedHandler( this.OnFileRenamed(virtualPath, destinationPath, isDirectory); } + private void NotifyHardlinkCreated( + string relativePath, + string destinationPath) + { + this.OnHardLinkCreated(relativePath, destinationPath); + } + private void NotifyFileHandleClosedFileModifiedOrDeletedHandler( string virtualPath, bool isDirectory, diff --git a/GVFS/GVFS.Virtualization/FileSystem/FileSystemVirtualizer.cs b/GVFS/GVFS.Virtualization/FileSystem/FileSystemVirtualizer.cs index acd21633b..739c91f54 100644 --- a/GVFS/GVFS.Virtualization/FileSystem/FileSystemVirtualizer.cs +++ b/GVFS/GVFS.Virtualization/FileSystem/FileSystemVirtualizer.cs @@ -242,6 +242,29 @@ protected void OnFileRenamed(string relativeSourcePath, string relativeDestinati } } + protected void OnHardLinkCreated(string relativeTargetPath, string relativeNewLinkPath) + { + try + { + bool pathInDotGit = FileSystemCallbacks.IsPathInsideDotGit(relativeNewLinkPath); + + if (pathInDotGit) + { + this.OnDotGitFileOrFolderChanged(relativeNewLinkPath); + } + else + { + this.FileSystemCallbacks.OnFileHardLinkCreated(relativeNewLinkPath); + } + } + catch (Exception e) + { + EventMetadata metadata = this.CreateEventMetadata(relativeNewLinkPath, e); + metadata.Add(nameof(relativeTargetPath), relativeTargetPath); + this.LogUnhandledExceptionAndExit(nameof(this.OnHardLinkCreated), metadata); + } + } + protected EventMetadata CreateEventMetadata( Guid enumerationId, string relativePath = null, From 67eebcd051efd0cbf7a321c890bce090f8b6ae83 Mon Sep 17 00:00:00 2001 From: William Baker Date: Tue, 21 Aug 2018 10:49:24 -0700 Subject: [PATCH 06/10] Add hardlink creation functional tests --- .../FileSystemRunners/BashRunner.cs | 13 +++++ .../FileSystemRunners/CmdRunner.cs | 10 ++++ .../FileSystemRunners/FileSystemRunner.cs | 4 ++ .../FileSystemRunners/PowerShellRunner.cs | 11 ++++ .../FileSystemRunners/SystemIORunner.cs | 10 ++++ .../EnlistmentPerFixture/GitFilesTests.cs | 51 ++++++++++++++----- .../Tests/GitCommands/AddStageTests.cs | 20 +++++++- .../Tests/GitCommands/GitRepoTests.cs | 10 ++++ 8 files changed, 115 insertions(+), 14 deletions(-) diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs index 6b2310a70..3a33258e0 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs @@ -47,6 +47,11 @@ public BashRunner() } } + public override bool SupportsHardlinkCreation + { + get { return true; } + } + protected override string FileName { get @@ -158,6 +163,14 @@ public override void CreateEmptyFile(string path) this.RunProcess(string.Format("-c \"touch {0}\"", bashPath)); } + public override void CreateHardLink(string existingPath, string newLinkPath) + { + string existingFileBashPath = this.ConvertWinPathToBashPath(existingPath); + string newLinkBashPath = this.ConvertWinPathToBashPath(existingPath); + + this.RunProcess(string.Format("-c \"ln {0} {1}\"", existingFileBashPath, newLinkBashPath)); + } + public override void WriteAllText(string path, string contents) { string bashPath = this.ConvertWinPathToBashPath(path); diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/CmdRunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/CmdRunner.cs index cb14a0bec..cf66c5f87 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/CmdRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/CmdRunner.cs @@ -27,6 +27,11 @@ 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 @@ -108,6 +113,11 @@ public override void CreateEmptyFile(string path) this.RunProcess(string.Format("/C type NUL > \"{0}\"", path)); } + public override void CreateHardLink(string targetPath, string newLinkPath) + { + this.RunProcess(string.Format("/C mklink /H \"{0}\" \"{1}\"", newLinkPath, targetPath)); + } + public override void AppendAllText(string path, string contents) { // Use echo|set /p with "" to avoid adding any trailing whitespace or newline diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs index 7679f4fe7..b7c99068e 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs @@ -46,6 +46,8 @@ public static FileSystemRunner DefaultRunner get { return defaultRunner; } } + public abstract bool SupportsHardlinkCreation { get; } + // File methods public abstract bool FileExists(string path); public abstract string MoveFile(string sourcePath, string targetPath); @@ -69,6 +71,8 @@ public static FileSystemRunner DefaultRunner public abstract void CreateEmptyFile(string path); + public abstract void CreateHardLink(string targetPath, string newLinkPath); + /// /// Write the specified contents to the specified file. By calling this method the caller is /// indicating that they expect the write to succeed. However, the caller is responsible for verifying that diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/PowerShellRunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/PowerShellRunner.cs index 9d6a1da97..cd0652f9a 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/PowerShellRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/PowerShellRunner.cs @@ -1,4 +1,5 @@ using GVFS.Tests.Should; +using NUnit.Framework; using System.IO; namespace GVFS.FunctionalTests.FileSystemRunners @@ -32,6 +33,11 @@ public class PowerShellRunner : ShellRunner "PermissionDenied" }; + public override bool SupportsHardlinkCreation + { + get { return false; } + } + protected override string FileName { get @@ -105,6 +111,11 @@ public override void CreateEmptyFile(string path) this.RunProcess(string.Format("-Command \"&{{ New-Item -ItemType file {0}}}\"", path)); } + public override void CreateHardLink(string targetPath, string newLinkPath) + { + Assert.Fail($"{nameof(PowerShellRunner)} does not support {nameof(this.CreateHardLink)}"); + } + 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 8ad8bdffe..53325a8e9 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/SystemIORunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/SystemIORunner.cs @@ -9,6 +9,11 @@ namespace GVFS.FunctionalTests.FileSystemRunners { public class SystemIORunner : FileSystemRunner { + public override bool SupportsHardlinkCreation + { + get { return false; } + } + public override bool FileExists(string path) { return File.Exists(path); @@ -75,6 +80,11 @@ public override void CreateEmptyFile(string path) } } + public override void CreateHardLink(string targetPath, string newLinkPath) + { + Assert.Fail($"{nameof(SystemIORunner)} does not support {nameof(this.CreateHardLink)}"); + } + public override void WriteAllText(string path, string contents) { File.WriteAllText(path, contents); diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs index 7abf164af..e18ef52db 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs @@ -38,10 +38,35 @@ public void CreateFileTest() this.fileSystem.CreateEmptyFile(this.Enlistment.GetVirtualPathTo(emptyFileName)); this.Enlistment.WaitForBackgroundOperations().ShouldEqual(true, "Background operations failed to complete."); GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, emptyFileName); - this.Enlistment.GetVirtualPathTo(fileName).ShouldBeAFile(this.fileSystem); + this.Enlistment.GetVirtualPathTo(emptyFileName).ShouldBeAFile(this.fileSystem); } [TestCase, Order(2)] + public void CreateHardLinkTest() + { + if (!this.fileSystem.SupportsHardlinkCreation) + { + return; + } + + string targetFileName = "hardLinkTarget.txt"; + string targetFilePath = this.Enlistment.GetVirtualPathTo(targetFileName); + GVFSHelpers.ModifiedPathsShouldNotContain(this.fileSystem, this.Enlistment.DotGVFSRoot, targetFileName); + this.fileSystem.WriteAllText(targetFilePath, "Some content here"); + this.Enlistment.WaitForBackgroundOperations().ShouldEqual(true, "Background operations failed to complete."); + GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, targetFileName); + targetFilePath.ShouldBeAFile(this.fileSystem).WithContents("Some content here"); + + string linkFileName = "hardLink.txt"; + string linkFilePath = this.Enlistment.GetVirtualPathTo(linkFileName); + GVFSHelpers.ModifiedPathsShouldNotContain(this.fileSystem, this.Enlistment.DotGVFSRoot, linkFileName); + this.fileSystem.CreateHardLink(targetFilePath, linkFilePath); + this.Enlistment.WaitForBackgroundOperations().ShouldEqual(true, "Background operations failed to complete."); + GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, linkFileName); + linkFilePath.ShouldBeAFile(this.fileSystem).WithContents("Some content here"); + } + + [TestCase, Order(3)] [Category(Categories.Mac.M2TODO)] public void CreateFileInFolderTest() { @@ -62,7 +87,7 @@ public void CreateFileInFolderTest() GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, folderName + "/" + fileName); } - [TestCase, Order(3)] + [TestCase, Order(4)] [Category(Categories.Mac.M2TODO)] public void RenameEmptyFolderTest() { @@ -83,7 +108,7 @@ public void RenameEmptyFolderTest() GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, expectedModifiedEntries); } - [TestCase, Order(4)] + [TestCase, Order(5)] [Category(Categories.Mac.M2TODO)] public void RenameFolderTest() { @@ -116,7 +141,7 @@ public void RenameFolderTest() GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, expectedModifiedEntries); } - [TestCase, Order(5)] + [TestCase, Order(6)] [Category(Categories.Mac.M2TODO)] public void CaseOnlyRenameOfNewFolderKeepsExcludeEntries() { @@ -138,7 +163,7 @@ public void CaseOnlyRenameOfNewFolderKeepsExcludeEntries() GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, expectedModifiedPathsEntries); } - [TestCase, Order(6)] + [TestCase, Order(7)] public void ReadingFileDoesNotUpdateIndexOrSparseCheckout() { string gitFileToCheck = "GVFS/GVFS.FunctionalTests/Category/CategoryConstants.cs"; @@ -166,7 +191,7 @@ public void ReadingFileDoesNotUpdateIndexOrSparseCheckout() } // TODO(Mac): Enable this test once the LockHolder is converted to .NET Core - [TestCase, Order(7)] + [TestCase, Order(8)] [Category(Categories.Mac.M2TODO)] public void ModifiedFileWillGetAddedToModifiedPathsFile() { @@ -186,7 +211,7 @@ public void ModifiedFileWillGetAddedToModifiedPathsFile() this.VerifyWorktreeBit(gitFileToTest, LsFilesStatus.Cached); } - [TestCase, Order(8)] + [TestCase, Order(9)] public void RenamedFileAddedToModifiedPathsFile() { string fileToRenameEntry = "Test_EPF_MoveRenameFileTests/ChangeUnhydratedFileName/Program.cs"; @@ -205,7 +230,7 @@ public void RenamedFileAddedToModifiedPathsFile() this.VerifyWorktreeBit(fileToRenameEntry, LsFilesStatus.Cached); } - [TestCase, Order(9)] + [TestCase, Order(10)] public void RenamedFileAndOverwrittenTargetAddedToModifiedPathsFile() { string fileToRenameEntry = "Test_EPF_MoveRenameFileTests_2/MoveUnhydratedFileToOverwriteUnhydratedFileAndWrite/RunUnitTests.bat"; @@ -226,7 +251,7 @@ public void RenamedFileAndOverwrittenTargetAddedToModifiedPathsFile() this.VerifyWorktreeBit(fileToRenameTargetEntry, LsFilesStatus.Cached); } - [TestCase, Order(10)] + [TestCase, Order(11)] public void DeletedFileAddedToModifiedPathsFile() { string fileToDeleteEntry = "GVFlt_DeleteFileTest/GVFlt_DeleteFullFileWithoutFileContext_DeleteOnClose/a.txt"; @@ -241,7 +266,7 @@ public void DeletedFileAddedToModifiedPathsFile() this.VerifyWorktreeBit(fileToDeleteEntry, LsFilesStatus.Cached); } - [TestCase, Order(11)] + [TestCase, Order(12)] public void DeletedFolderAndChildrenAddedToToModifiedPathsFile() { string folderToDelete = "Scripts"; @@ -273,7 +298,7 @@ public void DeletedFolderAndChildrenAddedToToModifiedPathsFile() } } - [TestCase, Order(12)] + [TestCase, Order(13)] public void FileRenamedOutOfRepoAddedToModifiedPathsFile() { string fileToRenameEntry = "GVFlt_MoveFileTest/PartialToOutside/from/lessInFrom.txt"; @@ -292,7 +317,7 @@ public void FileRenamedOutOfRepoAddedToModifiedPathsFile() this.VerifyWorktreeBit(fileToRenameEntry, LsFilesStatus.Cached); } - [TestCase, Order(13)] + [TestCase, Order(14)] public void OverwrittenFileAddedToSparseCheckoutAndSkipWorktreeBitCleared() { string fileToOverwriteEntry = "Test_EPF_WorkingDirectoryTests/1/2/3/4/ReadDeepProjectedFile.cpp"; @@ -312,7 +337,7 @@ public void OverwrittenFileAddedToSparseCheckoutAndSkipWorktreeBitCleared() this.VerifyWorktreeBit(fileToOverwriteEntry, LsFilesStatus.Cached); } - [TestCase, Order(14)] + [TestCase, Order(15)] [Category(Categories.Mac.M2TODO)] public void SupersededFileAddedToSparseCheckoutAndSkipWorktreeBitCleared() { diff --git a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/AddStageTests.cs b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/AddStageTests.cs index f079abe29..9e9e755bc 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/AddStageTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/AddStageTests.cs @@ -32,12 +32,30 @@ public void StageBasicTest() } [TestCase, Order(3)] + [Category(Categories.Mac.M2)] + public void AddAndStageHardLinksTest() + { + if (!this.FileSystem.SupportsHardlinkCreation) + { + return; + } + + this.CreateHardLink("Readme.md", "ReadmeLink.md"); + this.ValidateGitCommand("add ReadmeLink.md"); + this.RunGitCommand("commit -m \"Created ReadmeLink.md\""); + + this.CreateHardLink("AuthoringTests.md", "AuthoringTestsLink.md"); + this.ValidateGitCommand("stage AuthoringTestsLink.md"); + this.RunGitCommand("commit -m \"Created AuthoringTestsLink.md\""); + } + + [TestCase, Order(4)] public void AddAllowsPlaceholderCreation() { this.CommandAllowsPlaceholderCreation("add", @"GVFS\GVFS\Program.cs"); } - [TestCase, Order(4)] + [TestCase, Order(5)] public void StageAllowsPlaceholderCreation() { this.CommandAllowsPlaceholderCreation("stage", @"GVFS\GVFS\App.config"); diff --git a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs index 6dd862117..29c15d888 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs @@ -215,6 +215,16 @@ protected void EditFile(string filePath, string content) this.FileSystem.AppendAllText(controlFile, content); } + protected void CreateHardLink(string targetPath, string newLinkPath) + { + string virtualTargetFile = Path.Combine(this.Enlistment.RepoRoot, targetPath); + string controlTargetFile = Path.Combine(this.ControlGitRepo.RootPath, targetPath); + string virtualNewLinkFile = Path.Combine(this.Enlistment.RepoRoot, newLinkPath); + string controlNewLinkFile = Path.Combine(this.ControlGitRepo.RootPath, newLinkPath); + this.FileSystem.CreateHardLink(virtualTargetFile, virtualNewLinkFile); + this.FileSystem.CreateHardLink(controlTargetFile, controlNewLinkFile); + } + protected void SetFileAsReadOnly(string filePath) { string virtualFile = Path.Combine(this.Enlistment.RepoRoot, filePath); From 635f4d86376f352ca98f170016b26bfb105532ea Mon Sep 17 00:00:00 2001 From: William Baker Date: Tue, 21 Aug 2018 15:50:52 -0700 Subject: [PATCH 07/10] Cleanup after rebasing --- .../PrjFSKext/PrjFSKext/KauthHandler.cpp | 29 +++----- ProjFS.Mac/PrjFSLib/PrjFSLib.cpp | 71 ++++++++++--------- 2 files changed, 46 insertions(+), 54 deletions(-) diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp index f7f9fc6ff..ab288448d 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp @@ -35,9 +35,9 @@ static int HandleFileOpOperation( static int GetPid(vfs_context_t context); static uint32_t ReadVNodeFileFlags(vnode_t vn, vfs_context_t context); -static inline bool FileFlagsBitIsSet(uint32_t fileFlags, uint32_t bit); -static inline bool FileIsFlaggedAsInRoot(vnode_t vnode, vfs_context_t context); -static inline bool ActionBitIsSet(kauth_action_t action, kauth_action_t mask); +static bool FileFlagsBitIsSet(uint32_t fileFlags, uint32_t bit); +static bool FileIsFlaggedAsInRoot(vnode_t vnode, vfs_context_t context); +static bool ActionBitIsSet(kauth_action_t action, kauth_action_t mask); static bool IsFileSystemCrawler(char* procname); @@ -394,17 +394,6 @@ static int HandleFileOpOperation( goto CleanupAndReturn; } - vtype vnodeType = vnode_vtype(currentVnodeFromPath); - if (ShouldIgnoreVnodeType(vnodeType, currentVnodeFromPath)) - { - goto CleanupAndReturn; - } - - if (!HasAncestorFlaggedAsInRoot(currentVnodeFromPath, context)) - { - goto CleanupAndReturn; - } - VirtualizationRoot* root = nullptr; int pid; if (!ShouldHandleFileOpEvent( @@ -450,12 +439,6 @@ static int HandleFileOpOperation( // arg1 is the (const char *) path int closeFlags = static_cast(arg2); - vtype vnodeType = vnode_vtype(currentVnode); - if (ShouldIgnoreVnodeType(vnodeType, currentVnode)) - { - goto CleanupAndReturn; - } - if (vnode_isdir(currentVnode)) { goto CleanupAndReturn; @@ -630,6 +613,12 @@ static bool ShouldHandleFileOpEvent( VirtualizationRoot** root, int* pid) { + vtype vnodeType = vnode_vtype(vnode); + if (ShouldIgnoreVnodeType(vnodeType, vnode)) + { + return false; + } + *root = VirtualizationRoots_FindForVnode(vnode); if (nullptr == *root) { diff --git a/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp b/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp index 36be88ef3..79da6c482 100644 --- a/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp +++ b/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp @@ -44,6 +44,8 @@ template static bool InitializeEmptyPlaceholder(const cha static bool AddXAttr(const char* path, const char* name, const void* value, size_t size); static bool GetXAttr(const char* path, const char* name, size_t size, _Out_ void* value); +static PrjFS_NotificationType KUMessageTypeToNotificationType(MessageType kuNotificationType); + static bool IsVirtualizationRoot(const char* path); static void CombinePaths(const char* root, const char* relative, char (&combined)[PrjFSMaxPath]); @@ -422,15 +424,6 @@ static void HandleKernelRequest(Message request, void* messageMemory) } case MessageType_KtoU_NotifyFileModified: - { - result = HandleFileNotification( - requestHeader, - request.path, - false, // isDirectory - PrjFS_NotificationType_FileModified); - break; - } - case MessageType_KtoU_NotifyFilePreDelete: case MessageType_KtoU_NotifyDirectoryPreDelete: { @@ -438,24 +431,11 @@ static void HandleKernelRequest(Message request, void* messageMemory) requestHeader, request.path, requestHeader->messageType == MessageType_KtoU_NotifyDirectoryPreDelete, // isDirectory - PrjFS_NotificationType_PreDelete); + KUMessageTypeToNotificationType(static_cast(requestHeader->messageType))); break; } case MessageType_KtoU_NotifyFileCreated: - { - char fullPath[PrjFSMaxPath]; - CombinePaths(s_virtualizationRootFullPath.c_str(), request.path, fullPath); - SetBitInFileFlags(fullPath, FileFlags_IsInVirtualizationRoot, true); - - result = HandleFileNotification( - requestHeader, - request.path, - false, // isDirectory - PrjFS_NotificationType_NewFileCreated); - break; - } - case MessageType_KtoU_NotifyFileRenamed: case MessageType_KtoU_NotifyDirectoryRenamed: case MessageType_KtoU_NotifyFileHardLinkCreated: @@ -467,21 +447,11 @@ static void HandleKernelRequest(Message request, void* messageMemory) SetBitInFileFlags(fullPath, FileFlags_IsInVirtualizationRoot, true); bool isDirectory = requestHeader->messageType == MessageType_KtoU_NotifyDirectoryRenamed; - PrjFS_NotificationType notificationType; - if (requestHeader->messageType == MessageType_KtoU_NotifyFileHardLinkCreated) - { - notificationType = PrjFS_NotificationType_HardLinkCreated; - } - else - { - notificationType = PrjFS_NotificationType_FileRenamed; - } - result = HandleFileNotification( requestHeader, request.path, isDirectory, - notificationType); + KUMessageTypeToNotificationType(static_cast(requestHeader->messageType))); break; } } @@ -744,6 +714,39 @@ static bool GetXAttr(const char* path, const char* name, size_t size, _Out_ void return false; } +static inline PrjFS_NotificationType KUMessageTypeToNotificationType(MessageType kuNotificationType) +{ + switch(kuNotificationType) + { + case MessageType_KtoU_NotifyFileModified: + return PrjFS_NotificationType_FileModified; + + case MessageType_KtoU_NotifyFilePreDelete: + case MessageType_KtoU_NotifyDirectoryPreDelete: + return PrjFS_NotificationType_PreDelete; + + case MessageType_KtoU_NotifyFileCreated: + return PrjFS_NotificationType_NewFileCreated; + + case MessageType_KtoU_NotifyFileRenamed: + case MessageType_KtoU_NotifyDirectoryRenamed: + return PrjFS_NotificationType_FileRenamed; + + case MessageType_KtoU_NotifyFileHardLinkCreated: + return PrjFS_NotificationType_HardLinkCreated; + + // Non-notification types + case MessageType_Invalid: + case MessageType_UtoK_StartVirtualizationInstance: + case MessageType_UtoK_StopVirtualizationInstance: + case MessageType_KtoU_EnumerateDirectory: + case MessageType_KtoU_HydrateFile: + case MessageType_Response_Success: + case MessageType_Response_Fail: + return PrjFS_NotificationType_Invalid; + } +} + static errno_t SendKernelMessageResponse(uint64_t messageId, MessageType responseType) { const uint64_t inputs[] = { messageId, responseType }; From a5bea1ef291f8cf318d61a907d771b939f42c47b Mon Sep 17 00:00:00 2001 From: William Baker Date: Wed, 22 Aug 2018 11:56:21 -0700 Subject: [PATCH 08/10] Register for HardLinkCreated on Windows --- GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs b/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs index 65f5d67ad..5654a6a69 100644 --- a/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs +++ b/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs @@ -1365,19 +1365,23 @@ private class Notifications NotificationType.PreRename | NotificationType.PreDelete | NotificationType.FileRenamed | + NotificationType.HardlinkCreated | NotificationType.FileHandleClosedFileModified; public const NotificationType LogsHeadFile = - NotificationType.FileRenamed | + NotificationType.FileRenamed | + NotificationType.HardlinkCreated | NotificationType.FileHandleClosedFileModified; public const NotificationType ExcludeAndHeadFile = NotificationType.FileRenamed | + NotificationType.HardlinkCreated | NotificationType.FileHandleClosedFileDeleted | NotificationType.FileHandleClosedFileModified; public const NotificationType FilesAndFoldersInRefsHeads = NotificationType.FileRenamed | + NotificationType.HardlinkCreated | NotificationType.FileHandleClosedFileDeleted | NotificationType.FileHandleClosedFileModified; @@ -1385,6 +1389,7 @@ private class Notifications NotificationType.NewFileCreated | NotificationType.FileSupersededOrOverwritten | NotificationType.FileRenamed | + NotificationType.HardlinkCreated | NotificationType.FileHandleClosedFileDeleted | NotificationType.FilePreConvertToFull | NotificationType.FileHandleClosedFileModified; From b4fb4498270ca54b837ab0a73406af5285a03883 Mon Sep 17 00:00:00 2001 From: William Baker Date: Wed, 22 Aug 2018 14:16:18 -0700 Subject: [PATCH 09/10] PR Feedback: Code cleanup and test fixes --- .../FileSystem/IPlatformFileSystem.cs | 2 +- GVFS/GVFS.Common/NativeMethods.cs | 8 +-- .../FileSystemRunners/BashRunner.cs | 2 +- .../FileSystemRunners/FileSystemRunner.cs | 13 +++-- .../FileSystemRunners/PowerShellRunner.cs | 11 ---- .../FileSystemRunners/SystemIORunner.cs | 10 ---- .../EnlistmentPerFixture/GitFilesTests.cs | 51 +++++-------------- .../Tests/GitCommands/GitRepoTests.cs | 12 ++++- .../MacFileSystemVirtualizer.cs | 7 +-- .../WindowsFileSystemVirtualizer.cs | 6 +-- .../FileSystem/FileSystemVirtualizer.cs | 4 +- .../WindowsFileSystemVirtualizer.cs | 10 ++-- .../PrjFSKext/PrjFSKext/KauthHandler.cpp | 26 +++++----- ProjFS.Mac/PrjFSLib/PrjFSLib.cpp | 2 +- 14 files changed, 68 insertions(+), 96 deletions(-) diff --git a/GVFS/GVFS.Common/FileSystem/IPlatformFileSystem.cs b/GVFS/GVFS.Common/FileSystem/IPlatformFileSystem.cs index fd7f5ec37..93c8cb2e9 100644 --- a/GVFS/GVFS.Common/FileSystem/IPlatformFileSystem.cs +++ b/GVFS/GVFS.Common/FileSystem/IPlatformFileSystem.cs @@ -5,7 +5,7 @@ public interface IPlatformFileSystem bool SupportsFileMode { get; } void FlushFileBuffers(string path); void MoveAndOverwriteFile(string sourceFileName, string destinationFilename); - void CreateHardLink(string newFileName, string existingFileName); + void CreateHardLink(string newLinkFileName, string existingFileName); bool TryGetNormalizedPath(string path, out string normalizedPath, out string errorMessage); void ChangeMode(string path, int mode); } diff --git a/GVFS/GVFS.Common/NativeMethods.cs b/GVFS/GVFS.Common/NativeMethods.cs index c4564e44d..4f19f1e25 100644 --- a/GVFS/GVFS.Common/NativeMethods.cs +++ b/GVFS/GVFS.Common/NativeMethods.cs @@ -98,11 +98,11 @@ public static void MoveFile(string existingFileName, string newFileName, MoveFil } } - public static void CreateHardLink(string newFileName, string existingFileName) + public static void CreateHardLink(string newLinkFileName, string existingFileName) { - if (!CreateHardLink(newFileName, existingFileName, IntPtr.Zero)) + if (!CreateHardLink(newLinkFileName, existingFileName, IntPtr.Zero)) { - ThrowLastWin32Exception($"Failed to create hard link from '{newFileName}' to '{existingFileName}'"); + ThrowLastWin32Exception($"Failed to create hard link from '{newLinkFileName}' to '{existingFileName}'"); } } @@ -172,7 +172,7 @@ private static extern bool MoveFileEx( [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern bool CreateHardLink( - string newFileName, + string newLinkFileName, string existingFileName, IntPtr securityAttributes); diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs index 3a33258e0..e02bca468 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs @@ -166,7 +166,7 @@ public override void CreateEmptyFile(string path) public override void CreateHardLink(string existingPath, string newLinkPath) { string existingFileBashPath = this.ConvertWinPathToBashPath(existingPath); - string newLinkBashPath = this.ConvertWinPathToBashPath(existingPath); + string newLinkBashPath = this.ConvertWinPathToBashPath(newLinkPath); this.RunProcess(string.Format("-c \"ln {0} {1}\"", existingFileBashPath, newLinkBashPath)); } diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs index b7c99068e..3a4a37f0e 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs @@ -1,4 +1,5 @@ -using System; +using NUnit.Framework; +using System; namespace GVFS.FunctionalTests.FileSystemRunners { @@ -46,7 +47,10 @@ public static FileSystemRunner DefaultRunner get { return defaultRunner; } } - public abstract bool SupportsHardlinkCreation { get; } + public virtual bool SupportsHardlinkCreation + { + get { return false; } + } // File methods public abstract bool FileExists(string path); @@ -71,7 +75,10 @@ public static FileSystemRunner DefaultRunner public abstract void CreateEmptyFile(string path); - public abstract void CreateHardLink(string targetPath, string newLinkPath); + public virtual void CreateHardLink(string targetPath, string newLinkPath) + { + Assert.Fail($"This runner does not support {nameof(this.CreateHardLink)}"); + } /// /// 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 cd0652f9a..9d6a1da97 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/PowerShellRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/PowerShellRunner.cs @@ -1,5 +1,4 @@ using GVFS.Tests.Should; -using NUnit.Framework; using System.IO; namespace GVFS.FunctionalTests.FileSystemRunners @@ -33,11 +32,6 @@ public class PowerShellRunner : ShellRunner "PermissionDenied" }; - public override bool SupportsHardlinkCreation - { - get { return false; } - } - protected override string FileName { get @@ -111,11 +105,6 @@ public override void CreateEmptyFile(string path) this.RunProcess(string.Format("-Command \"&{{ New-Item -ItemType file {0}}}\"", path)); } - public override void CreateHardLink(string targetPath, string newLinkPath) - { - Assert.Fail($"{nameof(PowerShellRunner)} does not support {nameof(this.CreateHardLink)}"); - } - 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 53325a8e9..8ad8bdffe 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/SystemIORunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/SystemIORunner.cs @@ -9,11 +9,6 @@ namespace GVFS.FunctionalTests.FileSystemRunners { public class SystemIORunner : FileSystemRunner { - public override bool SupportsHardlinkCreation - { - get { return false; } - } - public override bool FileExists(string path) { return File.Exists(path); @@ -80,11 +75,6 @@ public override void CreateEmptyFile(string path) } } - public override void CreateHardLink(string targetPath, string newLinkPath) - { - Assert.Fail($"{nameof(SystemIORunner)} does not support {nameof(this.CreateHardLink)}"); - } - public override void WriteAllText(string path, string contents) { File.WriteAllText(path, contents); diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs index e18ef52db..72e3fab84 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs @@ -3,7 +3,6 @@ using GVFS.FunctionalTests.Tools; using GVFS.Tests.Should; using NUnit.Framework; -using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -12,7 +11,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture { - [TestFixtureSource(typeof(GitFilesTestsRunners), GitFilesTestsRunners.TestRunners)] + [TestFixtureSource(typeof(FileSystemRunner), FileSystemRunner.TestRunners)] [Category(Categories.Mac.M2)] public class GitFilesTests : TestsWithEnlistmentPerFixture { @@ -49,21 +48,21 @@ public void CreateHardLinkTest() return; } - string targetFileName = "hardLinkTarget.txt"; - string targetFilePath = this.Enlistment.GetVirtualPathTo(targetFileName); - GVFSHelpers.ModifiedPathsShouldNotContain(this.fileSystem, this.Enlistment.DotGVFSRoot, targetFileName); - this.fileSystem.WriteAllText(targetFilePath, "Some content here"); + string existingFileName = "fileToLinkTo.txt"; + string existingFilePath = this.Enlistment.GetVirtualPathTo(existingFileName); + GVFSHelpers.ModifiedPathsShouldNotContain(this.fileSystem, this.Enlistment.DotGVFSRoot, existingFileName); + this.fileSystem.WriteAllText(existingFilePath, "Some content here"); this.Enlistment.WaitForBackgroundOperations().ShouldEqual(true, "Background operations failed to complete."); - GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, targetFileName); - targetFilePath.ShouldBeAFile(this.fileSystem).WithContents("Some content here"); + GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, existingFileName); + existingFilePath.ShouldBeAFile(this.fileSystem).WithContents("Some content here"); - string linkFileName = "hardLink.txt"; - string linkFilePath = this.Enlistment.GetVirtualPathTo(linkFileName); - GVFSHelpers.ModifiedPathsShouldNotContain(this.fileSystem, this.Enlistment.DotGVFSRoot, linkFileName); - this.fileSystem.CreateHardLink(targetFilePath, linkFilePath); + string newLinkFileName = "newHardLink.txt"; + string newLinkFilePath = this.Enlistment.GetVirtualPathTo(newLinkFileName); + GVFSHelpers.ModifiedPathsShouldNotContain(this.fileSystem, this.Enlistment.DotGVFSRoot, newLinkFileName); + this.fileSystem.CreateHardLink(existingFilePath, newLinkFilePath); this.Enlistment.WaitForBackgroundOperations().ShouldEqual(true, "Background operations failed to complete."); - GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, linkFileName); - linkFilePath.ShouldBeAFile(this.fileSystem).WithContents("Some content here"); + GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, newLinkFileName); + newLinkFilePath.ShouldBeAFile(this.fileSystem).WithContents("Some content here"); } [TestCase, Order(3)] @@ -376,29 +375,5 @@ private static class LsFilesStatus public const char Cached = 'H'; public const char SkipWorktree = 'S'; } - - private class GitFilesTestsRunners - { - public const string TestRunners = "Runners"; - - public static object[] Runners - { - get - { - // Don't use the BashRunner for GitFilesTests as the BashRunner always strips off the last trailing newline (\n) - // and we expect there to be a trailing new line - List runners = new List(); - foreach (object[] runner in FileSystemRunner.Runners.ToList()) - { - if (!(runner.ToList().First() is BashRunner)) - { - runners.Add(new object[] { runner.ToList().First() }); - } - } - - return runners.ToArray(); - } - } - } } } diff --git a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs index 29c15d888..f19ee2ee3 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs @@ -221,8 +221,16 @@ protected void CreateHardLink(string targetPath, string newLinkPath) string controlTargetFile = Path.Combine(this.ControlGitRepo.RootPath, targetPath); string virtualNewLinkFile = Path.Combine(this.Enlistment.RepoRoot, newLinkPath); string controlNewLinkFile = Path.Combine(this.ControlGitRepo.RootPath, newLinkPath); - this.FileSystem.CreateHardLink(virtualTargetFile, virtualNewLinkFile); - this.FileSystem.CreateHardLink(controlTargetFile, controlNewLinkFile); + + // GitRepoTests are only run with SystemIORunner (which does not support hardlink + // creation) so use a BashRunner instead. + this.FileSystem.ShouldBeOfType(); + this.FileSystem.SupportsHardlinkCreation.ShouldBeFalse( + "FileSystem *does* support hard link creation, CreateHardLink no longer needs to create a BashRunner"); + FileSystemRunner runner = new BashRunner(); + + runner.CreateHardLink(virtualTargetFile, virtualNewLinkFile); + runner.CreateHardLink(controlTargetFile, controlNewLinkFile); } protected void SetFileAsReadOnly(string filePath) diff --git a/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs b/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs index 330dad5ad..f358f07bb 100644 --- a/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs +++ b/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs @@ -352,8 +352,9 @@ private void OnNewFileCreated(string relativePath, bool isDirectory) private void OnFileRenamed(string relativeDestinationPath, bool isDirectory) { - // relativeSourcePath is handled in the OnPreDelete callback that's triggered - // prior to OnFileRenamed + // ProjFS for Mac *could* be updated to provide us with relativeSourcePath as well, + // but because VFSForGit doesn't need the source path on Mac for correct behavior + // the relativeSourcePath is left out of the notification to keep the kext simple this.OnFileRenamed( relativeSourcePath: string.Empty, relativeDestinationPath: relativeDestinationPath, @@ -363,7 +364,7 @@ private void OnFileRenamed(string relativeDestinationPath, bool isDirectory) private void OnHardLinkCreated(string relativeNewLinkPath) { this.OnHardLinkCreated( - relativeTargetPath: string.Empty, + relativeExistingFilePath: string.Empty, relativeNewLinkPath: relativeNewLinkPath); } diff --git a/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs b/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs index 5654a6a69..6503194c0 100644 --- a/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs +++ b/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs @@ -1227,10 +1227,10 @@ private void NotifyFileRenamedHandler( } private void NotifyHardlinkCreated( - string relativePath, - string destinationPath) + string relativeExistingFilePath, + string relativeNewLinkPath) { - this.OnHardLinkCreated(relativePath, destinationPath); + this.OnHardLinkCreated(relativeExistingFilePath, relativeNewLinkPath); } private void NotifyFileHandleClosedFileModifiedOrDeletedHandler( diff --git a/GVFS/GVFS.Virtualization/FileSystem/FileSystemVirtualizer.cs b/GVFS/GVFS.Virtualization/FileSystem/FileSystemVirtualizer.cs index 739c91f54..4357d85a4 100644 --- a/GVFS/GVFS.Virtualization/FileSystem/FileSystemVirtualizer.cs +++ b/GVFS/GVFS.Virtualization/FileSystem/FileSystemVirtualizer.cs @@ -242,7 +242,7 @@ protected void OnFileRenamed(string relativeSourcePath, string relativeDestinati } } - protected void OnHardLinkCreated(string relativeTargetPath, string relativeNewLinkPath) + protected void OnHardLinkCreated(string relativeExistingFilePath, string relativeNewLinkPath) { try { @@ -260,7 +260,7 @@ protected void OnHardLinkCreated(string relativeTargetPath, string relativeNewLi catch (Exception e) { EventMetadata metadata = this.CreateEventMetadata(relativeNewLinkPath, e); - metadata.Add(nameof(relativeTargetPath), relativeTargetPath); + metadata.Add(nameof(relativeExistingFilePath), relativeExistingFilePath); this.LogUnhandledExceptionAndExit(nameof(this.OnHardLinkCreated), metadata); } } diff --git a/MirrorProvider/MirrorProvider.Windows/WindowsFileSystemVirtualizer.cs b/MirrorProvider/MirrorProvider.Windows/WindowsFileSystemVirtualizer.cs index edfd0acf1..5100884cf 100644 --- a/MirrorProvider/MirrorProvider.Windows/WindowsFileSystemVirtualizer.cs +++ b/MirrorProvider/MirrorProvider.Windows/WindowsFileSystemVirtualizer.cs @@ -321,19 +321,19 @@ private void OnFileModifiedOrDeleted(string relativePath, bool isDirectory, bool } private void OnFileRenamed( - string relativePath, + string relativeSourcePath, string relativeDestinationPath, bool isDirectory, ref NotificationType notificationMask) { - Console.WriteLine($"OnFileRenamed (isDirectory: {isDirectory}), relativePath: {relativePath}, relativeDestinationPath: {relativeDestinationPath}"); + Console.WriteLine($"OnFileRenamed (isDirectory: {isDirectory}), relativeSourcePath: {relativeSourcePath}, relativeDestinationPath: {relativeDestinationPath}"); } private void OnHardlinkCreated( - string relativePath, - string relativeDestinationPath) + string relativeExistingFilePath, + string relativeNewLinkFilePath) { - Console.WriteLine($"OnHardlinkCreated, relativePath: {relativePath}, relativeDestinationPath: {relativeDestinationPath}"); + Console.WriteLine($"OnHardlinkCreated, relativeExistingFilePath: {relativeExistingFilePath}, relativeNewLinkFilePath: {relativeNewLinkFilePath}"); } // TODO: Add this to the ProjFS API diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp index ab288448d..c5d749782 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp @@ -35,9 +35,9 @@ static int HandleFileOpOperation( static int GetPid(vfs_context_t context); static uint32_t ReadVNodeFileFlags(vnode_t vn, vfs_context_t context); -static bool FileFlagsBitIsSet(uint32_t fileFlags, uint32_t bit); -static bool FileIsFlaggedAsInRoot(vnode_t vnode, vfs_context_t context); -static bool ActionBitIsSet(kauth_action_t action, kauth_action_t mask); +static inline bool FileFlagsBitIsSet(uint32_t fileFlags, uint32_t bit); +static inline bool FileIsFlaggedAsInRoot(vnode_t vnode, vfs_context_t context); +static inline bool ActionBitIsSet(kauth_action_t action, kauth_action_t mask); static bool IsFileSystemCrawler(char* procname); @@ -341,7 +341,8 @@ static int HandleVnodeOperation( KAUTH_VNODE_WRITE_EXTATTRIBUTES | KAUTH_VNODE_READ_DATA | KAUTH_VNODE_WRITE_DATA | - KAUTH_VNODE_EXECUTE)) + KAUTH_VNODE_EXECUTE | + KAUTH_VNODE_DELETE)) // Hydrate on delete to ensure files are hydrated before rename operations { if (FileFlagsBitIsSet(currentVnodeFileFlags, FileFlags_IsEmpty)) { @@ -387,7 +388,8 @@ static int HandleFileOpOperation( // arg0 is the (const char *) fromPath (or the file being linked to) const char* newPath = (const char*)arg1; - // TODO(Mac): Improve error handling + // TODO(Mac): We need to handle failures to lookup the vnode. If we fail to lookup the vnode + // it's possible that we'll miss notifications errno_t toErr = vnode_lookup(newPath, 0 /* flags */, ¤tVnodeFromPath, context); if (0 != toErr) { @@ -422,13 +424,13 @@ static int HandleFileOpOperation( int kauthResult; int kauthError; if (!TrySendRequestAndWaitForResponse( - root, - messageType, - currentVnodeFromPath, - pid, - procname, - &kauthResult, - &kauthError)) + root, + messageType, + currentVnodeFromPath, + pid, + procname, + &kauthResult, + &kauthError)) { goto CleanupAndReturn; } diff --git a/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp b/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp index 79da6c482..dd8ab7cec 100644 --- a/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp +++ b/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp @@ -44,7 +44,7 @@ template static bool InitializeEmptyPlaceholder(const cha static bool AddXAttr(const char* path, const char* name, const void* value, size_t size); static bool GetXAttr(const char* path, const char* name, size_t size, _Out_ void* value); -static PrjFS_NotificationType KUMessageTypeToNotificationType(MessageType kuNotificationType); +static inline PrjFS_NotificationType KUMessageTypeToNotificationType(MessageType kuNotificationType); static bool IsVirtualizationRoot(const char* path); static void CombinePaths(const char* root, const char* relative, char (&combined)[PrjFSMaxPath]); From 4e90178a079d5d12409d4b75a97cbac61d82de79 Mon Sep 17 00:00:00 2001 From: William Baker Date: Thu, 23 Aug 2018 10:45:30 -0700 Subject: [PATCH 10/10] More cleanup for PR comments --- .../FileSystemRunners/BashRunner.cs | 6 +++--- .../FileSystemRunners/CmdRunner.cs | 4 ++-- .../FileSystemRunners/FileSystemRunner.cs | 2 +- .../Tests/EnlistmentPerFixture/GitFilesTests.cs | 2 +- .../Tests/GitCommands/AddStageTests.cs | 4 ++-- .../Tests/GitCommands/GitRepoTests.cs | 17 ++++++++--------- 6 files changed, 17 insertions(+), 18 deletions(-) diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs index e02bca468..40f2fbc12 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs @@ -163,10 +163,10 @@ public override void CreateEmptyFile(string path) this.RunProcess(string.Format("-c \"touch {0}\"", bashPath)); } - public override void CreateHardLink(string existingPath, string newLinkPath) + public override void CreateHardLink(string newLinkFilePath, string existingFilePath) { - string existingFileBashPath = this.ConvertWinPathToBashPath(existingPath); - string newLinkBashPath = this.ConvertWinPathToBashPath(newLinkPath); + string existingFileBashPath = this.ConvertWinPathToBashPath(existingFilePath); + string newLinkBashPath = this.ConvertWinPathToBashPath(newLinkFilePath); this.RunProcess(string.Format("-c \"ln {0} {1}\"", existingFileBashPath, newLinkBashPath)); } diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/CmdRunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/CmdRunner.cs index cf66c5f87..cb5184c06 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/CmdRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/CmdRunner.cs @@ -113,9 +113,9 @@ public override void CreateEmptyFile(string path) this.RunProcess(string.Format("/C type NUL > \"{0}\"", path)); } - public override void CreateHardLink(string targetPath, string newLinkPath) + public override void CreateHardLink(string newLinkFilePath, string existingFilePath) { - this.RunProcess(string.Format("/C mklink /H \"{0}\" \"{1}\"", newLinkPath, targetPath)); + this.RunProcess(string.Format("/C mklink /H \"{0}\" \"{1}\"", newLinkFilePath, existingFilePath)); } public override void AppendAllText(string path, string contents) diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs index 3a4a37f0e..b8a2f9cc6 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs @@ -75,7 +75,7 @@ public virtual bool SupportsHardlinkCreation public abstract void CreateEmptyFile(string path); - public virtual void CreateHardLink(string targetPath, string newLinkPath) + public virtual void CreateHardLink(string newLinkFilePath, string existingFilePath) { Assert.Fail($"This runner does not support {nameof(this.CreateHardLink)}"); } diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs index 72e3fab84..5ec2ee3b3 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs @@ -59,7 +59,7 @@ public void CreateHardLinkTest() string newLinkFileName = "newHardLink.txt"; string newLinkFilePath = this.Enlistment.GetVirtualPathTo(newLinkFileName); GVFSHelpers.ModifiedPathsShouldNotContain(this.fileSystem, this.Enlistment.DotGVFSRoot, newLinkFileName); - this.fileSystem.CreateHardLink(existingFilePath, newLinkFilePath); + this.fileSystem.CreateHardLink(newLinkFilePath, existingFilePath); this.Enlistment.WaitForBackgroundOperations().ShouldEqual(true, "Background operations failed to complete."); GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, newLinkFileName); newLinkFilePath.ShouldBeAFile(this.fileSystem).WithContents("Some content here"); diff --git a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/AddStageTests.cs b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/AddStageTests.cs index 9e9e755bc..ac38b5e19 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/AddStageTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/AddStageTests.cs @@ -40,11 +40,11 @@ public void AddAndStageHardLinksTest() return; } - this.CreateHardLink("Readme.md", "ReadmeLink.md"); + this.CreateHardLink("ReadmeLink.md", "Readme.md"); this.ValidateGitCommand("add ReadmeLink.md"); this.RunGitCommand("commit -m \"Created ReadmeLink.md\""); - this.CreateHardLink("AuthoringTests.md", "AuthoringTestsLink.md"); + this.CreateHardLink("AuthoringTestsLink.md", "AuthoringTests.md"); this.ValidateGitCommand("stage AuthoringTestsLink.md"); this.RunGitCommand("commit -m \"Created AuthoringTestsLink.md\""); } diff --git a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs index f19ee2ee3..89fe43289 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs @@ -215,22 +215,21 @@ protected void EditFile(string filePath, string content) this.FileSystem.AppendAllText(controlFile, content); } - protected void CreateHardLink(string targetPath, string newLinkPath) + protected void CreateHardLink(string newLinkFileName, string existingFileName) { - string virtualTargetFile = Path.Combine(this.Enlistment.RepoRoot, targetPath); - string controlTargetFile = Path.Combine(this.ControlGitRepo.RootPath, targetPath); - string virtualNewLinkFile = Path.Combine(this.Enlistment.RepoRoot, newLinkPath); - string controlNewLinkFile = Path.Combine(this.ControlGitRepo.RootPath, newLinkPath); + string virtualExistingFile = Path.Combine(this.Enlistment.RepoRoot, existingFileName); + string controlExistingFile = Path.Combine(this.ControlGitRepo.RootPath, 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.ShouldBeOfType(); this.FileSystem.SupportsHardlinkCreation.ShouldBeFalse( - "FileSystem *does* support hard link creation, CreateHardLink no longer needs to create a BashRunner"); + "If this.FileSystem.SupportsHardlinkCreation is true, CreateHardLink no longer needs to create a BashRunner"); FileSystemRunner runner = new BashRunner(); - runner.CreateHardLink(virtualTargetFile, virtualNewLinkFile); - runner.CreateHardLink(controlTargetFile, controlNewLinkFile); + runner.CreateHardLink(virtualNewLinkFile, virtualExistingFile); + runner.CreateHardLink(controlNewLinkFile, controlExistingFile); } protected void SetFileAsReadOnly(string filePath)