From ee207754ce7ca1e35593b7f03a1710e4674440a3 Mon Sep 17 00:00:00 2001 From: William Baker Date: Fri, 17 Aug 2018 12:58:12 -0700 Subject: [PATCH 1/4] Mac: New file notifications --- .../EnlistmentPerFixture/GitFilesTests.cs | 12 +- .../GitMoveRenameTests.cs | 10 +- .../PersistedModifiedPathsTests.cs | 3 +- .../Tests/GitCommands/AddStageTests.cs | 1 + .../Tests/GitCommands/GitCommandsTests.cs | 52 +++-- .../Tests/GitCommands/HashObjectTests.cs | 1 + .../MacFileSystemVirtualizer.cs | 36 ++++ .../PrjFSKext/PrjFSKext/KauthHandler.cpp | 204 ++++++++---------- ProjFS.Mac/PrjFSKext/public/Message.h | 1 + .../VirtualizationInstance.cs | 7 +- ProjFS.Mac/PrjFSLib/PrjFSLib.cpp | 14 ++ 11 files changed, 200 insertions(+), 141 deletions(-) diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs index a7d31e54f..c2fac6926 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs @@ -24,17 +24,21 @@ public GitFilesTests(FileSystemRunner fileSystem) } [TestCase, Order(1)] - [Category(Categories.Mac.M2TODO)] public void CreateFileTest() { string fileName = "file1.txt"; GVFSHelpers.ModifiedPathsShouldNotContain(this.fileSystem, this.Enlistment.DotGVFSRoot, fileName); this.fileSystem.WriteAllText(this.Enlistment.GetVirtualPathTo(fileName), "Some content here"); - this.Enlistment.WaitForBackgroundOperations().ShouldEqual(true, "Background operations failed to complete."); - - this.Enlistment.GetVirtualPathTo(fileName).ShouldBeAFile(this.fileSystem).WithContents("Some content here"); GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, fileName); + this.Enlistment.GetVirtualPathTo(fileName).ShouldBeAFile(this.fileSystem).WithContents("Some content here"); + + string emptyFileName = "file1empty.txt"; + GVFSHelpers.ModifiedPathsShouldNotContain(this.fileSystem, this.Enlistment.DotGVFSRoot, emptyFileName); + 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); } [TestCase, Order(2)] diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitMoveRenameTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitMoveRenameTests.cs index 7ec766bb8..d0b1a09e4 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitMoveRenameTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitMoveRenameTests.cs @@ -32,14 +32,14 @@ public void GitStatus() } [TestCase, Order(2)] - [Category(Categories.Mac.M2TODO)] public void GitStatusAfterNewFile() { string filename = "new.cs"; + string filePath = this.Enlistment.GetVirtualPathTo(filename); - this.fileSystem.WriteAllText(this.Enlistment.GetVirtualPathTo(filename), this.testFileContents); + this.fileSystem.WriteAllText(filePath, this.testFileContents); - this.Enlistment.GetVirtualPathTo(filename).ShouldBeAFile(this.fileSystem).WithContents(this.testFileContents); + filePath.ShouldBeAFile(this.fileSystem).WithContents(this.testFileContents); GitHelpers.CheckGitCommandAgainstGVFSRepo( this.Enlistment.RepoRoot, @@ -47,6 +47,8 @@ public void GitStatusAfterNewFile() "On branch " + Properties.Settings.Default.Commitish, "Untracked files:", filename); + + this.fileSystem.DeleteFile(filePath); } [TestCase, Order(3)] @@ -54,7 +56,7 @@ public void GitStatusAfterNewFile() public void GitStatusAfterFileNameCaseChange() { string oldFilename = "new.cs"; - this.Enlistment.GetVirtualPathTo(oldFilename).ShouldBeAFile(this.fileSystem); + this.EnsureTestFileExists(oldFilename); string newFilename = "New.cs"; this.fileSystem.MoveFile(this.Enlistment.GetVirtualPathTo(oldFilename), this.Enlistment.GetVirtualPathTo(newFilename)); diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerTestCase/PersistedModifiedPathsTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerTestCase/PersistedModifiedPathsTests.cs index 57ecef8d1..dfc1055c7 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerTestCase/PersistedModifiedPathsTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerTestCase/PersistedModifiedPathsTests.cs @@ -10,8 +10,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerTestCase { [TestFixture] - //// TODO(Mac): Enable for M2 once notifications are working - //// [Category(Categories.Mac.M2)] + [Category(Categories.Mac.M2TODO)] public class PersistedModifiedPathsTests : TestsWithEnlistmentPerTestCase { private static readonly string FileToAdd = Path.Combine("GVFS", "TestAddFile.txt"); diff --git a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/AddStageTests.cs b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/AddStageTests.cs index eb9f74ab2..f079abe29 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/AddStageTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/AddStageTests.cs @@ -6,6 +6,7 @@ namespace GVFS.FunctionalTests.Tests.GitCommands { [TestFixture] [Category(Categories.GitCommands)] + [Category(Categories.Mac.M3)] public class AddStageTests : GitRepoTests { public AddStageTests() : base(enlistmentPerTest: false) diff --git a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitCommandsTests.cs b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitCommandsTests.cs index 1aac419aa..81383b9e5 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitCommandsTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitCommandsTests.cs @@ -88,48 +88,56 @@ public void RenameCurrentBranchTest() } [TestCase] + [Category(Categories.Mac.M2)] public void UntrackedFileTest() { this.BasicCommit(this.CreateFile, addCommand: "add ."); } [TestCase] + [Category(Categories.Mac.M2)] public void UntrackedEmptyFileTest() { this.BasicCommit(this.CreateEmptyFile, addCommand: "add ."); } [TestCase] + [Category(Categories.Mac.M2)] public void UntrackedFileAddAllTest() { this.BasicCommit(this.CreateFile, addCommand: "add --all"); } [TestCase] + [Category(Categories.Mac.M2)] public void UntrackedEmptyFileAddAllTest() { this.BasicCommit(this.CreateEmptyFile, addCommand: "add --all"); } [TestCase] + [Category(Categories.Mac.M2)] public void StageUntrackedFileTest() { this.BasicCommit(this.CreateFile, addCommand: "stage ."); } [TestCase] + [Category(Categories.Mac.M2)] public void StageUntrackedEmptyFileTest() { this.BasicCommit(this.CreateEmptyFile, addCommand: "stage ."); } [TestCase] + [Category(Categories.Mac.M2)] public void StageUntrackedFileAddAllTest() { this.BasicCommit(this.CreateFile, addCommand: "stage --all"); } [TestCase] + [Category(Categories.Mac.M2)] public void StageUntrackedEmptyFileAddAllTest() { this.BasicCommit(this.CreateEmptyFile, addCommand: "stage --all"); @@ -234,27 +242,33 @@ public void DeleteFolderCommitChangesSwitchBranchSwitchBackTest() } [TestCase] + [Category(Categories.Mac.M2)] public void DeleteFilesWithNameAheadOfDot() { - this.FolderShouldExistAndHaveFile("GitCommandsTests\\DeleteFileTests\\1", "#test"); - this.DeleteFile("GitCommandsTests\\DeleteFileTests\\1\\#test"); - this.FolderShouldExistAndBeEmpty("GitCommandsTests\\DeleteFileTests\\1"); - - this.FolderShouldExistAndHaveFile("GitCommandsTests\\DeleteFileTests\\2", "$test"); - this.DeleteFile("GitCommandsTests\\DeleteFileTests\\2\\$test"); - this.FolderShouldExistAndBeEmpty("GitCommandsTests\\DeleteFileTests\\2"); - - this.FolderShouldExistAndHaveFile("GitCommandsTests\\DeleteFileTests\\3", ")"); - this.DeleteFile("GitCommandsTests\\DeleteFileTests\\3\\)"); - this.FolderShouldExistAndBeEmpty("GitCommandsTests\\DeleteFileTests\\3"); - - this.FolderShouldExistAndHaveFile("GitCommandsTests\\DeleteFileTests\\4", "+.test"); - this.DeleteFile("GitCommandsTests\\DeleteFileTests\\4\\+.test"); - this.FolderShouldExistAndBeEmpty("GitCommandsTests\\DeleteFileTests\\4"); - - this.FolderShouldExistAndHaveFile("GitCommandsTests\\DeleteFileTests\\5", "-.test"); - this.DeleteFile("GitCommandsTests\\DeleteFileTests\\5\\-.test"); - this.FolderShouldExistAndBeEmpty("GitCommandsTests\\DeleteFileTests\\5"); + string folder = Path.Combine("GitCommandsTests", "DeleteFileTests", "1"); + this.FolderShouldExistAndHaveFile(folder, "#test"); + this.DeleteFile(Path.Combine(folder, "#test")); + this.FolderShouldExistAndBeEmpty(folder); + + folder = Path.Combine("GitCommandsTests", "DeleteFileTests", "2"); + this.FolderShouldExistAndHaveFile(folder, "$test"); + this.DeleteFile(Path.Combine(folder, "$test")); + this.FolderShouldExistAndBeEmpty(folder); + + folder = Path.Combine("GitCommandsTests", "DeleteFileTests", "3"); + this.FolderShouldExistAndHaveFile(folder, ")"); + this.DeleteFile(Path.Combine(folder, ")")); + this.FolderShouldExistAndBeEmpty(folder); + + folder = Path.Combine("GitCommandsTests", "DeleteFileTests", "4"); + this.FolderShouldExistAndHaveFile(folder, "+.test"); + this.DeleteFile(Path.Combine(folder, "+.test")); + this.FolderShouldExistAndBeEmpty(folder); + + folder = Path.Combine("GitCommandsTests", "DeleteFileTests", "5"); + this.FolderShouldExistAndHaveFile(folder, "-.test"); + this.DeleteFile(Path.Combine(folder, "-.test")); + this.FolderShouldExistAndBeEmpty(folder); this.ValidateGitCommand("status"); } diff --git a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/HashObjectTests.cs b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/HashObjectTests.cs index aa5d018ae..3c2bc5d86 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/HashObjectTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/HashObjectTests.cs @@ -5,6 +5,7 @@ namespace GVFS.FunctionalTests.Tests.GitCommands { [TestFixture] + [Category(Categories.Mac.M3)] public class HashObjectTests : GitRepoTests { public HashObjectTests() : base(enlistmentPerTest: false) diff --git a/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs b/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs index 0ffc5f495..66a7a83c9 100644 --- a/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs +++ b/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs @@ -101,6 +101,7 @@ protected override bool TryStart(out string error) this.virtualizationInstance.OnGetFileStream = this.OnGetFileStream; this.virtualizationInstance.OnFileModified = this.OnFileModified; this.virtualizationInstance.OnPreDelete = this.OnPreDelete; + this.virtualizationInstance.OnNewFileCreated = this.OnNewFileCreated; uint threadCount = (uint)Environment.ProcessorCount * 2; @@ -312,6 +313,41 @@ private Result OnPreDelete(string relativePath, bool isDirectory) return Result.Success; } + private void OnNewFileCreated(string relativePath, bool isDirectory) + { + try + { + if (!Virtualization.FileSystemCallbacks.IsPathInsideDotGit(relativePath)) + { + if (isDirectory) + { + GitCommandLineParser gitCommand = new GitCommandLineParser(this.Context.Repository.GVFSLock.GetLockedGitCommand()); + if (gitCommand.IsValidGitCommand) + { + // TODO(Mac): Ensure that when git creates a folder all files\folders within that folder are written to disk + EventMetadata metadata = this.CreateEventMetadata(relativePath); + metadata.Add("isDirectory", isDirectory); + this.Context.Tracer.RelatedWarning(metadata, $"{nameof(this.OnNewFileCreated)}: Git created a folder, currently an unsupported scenario on Mac"); + } + else + { + this.FileSystemCallbacks.OnFolderCreated(relativePath); + } + } + else + { + this.FileSystemCallbacks.OnFileCreated(relativePath); + } + } + } + catch (Exception e) + { + EventMetadata metadata = this.CreateEventMetadata(relativePath, e); + metadata.Add("isDirectory", isDirectory); + this.LogUnhandledExceptionAndExit(nameof(this.OnNewFileCreated), metadata); + } + } + private Result OnEnumerateDirectory( ulong commandId, string relativePath, diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp index 3288854ad..db177bb4a 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp @@ -35,9 +35,10 @@ 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 ActionBitIsSet(kauth_action_t action, kauth_action_t mask); -static bool ActionBitsNotSet(kauth_action_t action, kauth_action_t mask); +static inline bool HasAncestorFlaggedAsInRoot(vnode_t vnode, 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 IsFileSystemCrawler(char* procname); @@ -225,6 +226,7 @@ void KauthHandler_HandleKernelMessageResponse(uint64_t messageId, MessageType re case MessageType_KtoU_NotifyFileModified: case MessageType_KtoU_NotifyFilePreDelete: case MessageType_KtoU_NotifyDirectoryPreDelete: + case MessageType_KtoU_NotifyFileCreated: KextLog_Error("KauthHandler_HandleKernelMessageResponse: Unexpected responseType: %d", responseType); break; } @@ -382,28 +384,66 @@ static int HandleFileOpOperation( // arg1 is the (const char *) path int closeFlags = static_cast(arg2); - if (KAUTH_FILEOP_CLOSE_MODIFIED == closeFlags) + vtype vnodeType = vnode_vtype(currentVnode); + if (ShouldIgnoreVnodeType(vnodeType, currentVnode)) { - VirtualizationRoot* root = nullptr; - int pid; - if (!ShouldHandleFileOpEvent( - context, + goto CleanupAndReturn; + } + + if (vnode_isdir(currentVnode)) + { + goto CleanupAndReturn; + } + + bool fileFlaggedInRoot = FileIsFlaggedAsInRoot(currentVnode, context); + if (fileFlaggedInRoot && KAUTH_FILEOP_CLOSE_MODIFIED != closeFlags) + { + goto CleanupAndReturn; + } + + if (!fileFlaggedInRoot && !HasAncestorFlaggedAsInRoot(currentVnode, context)) + { + goto CleanupAndReturn; + } + + VirtualizationRoot* root = nullptr; + int pid; + if (!ShouldHandleFileOpEvent( + context, + currentVnode, + action, + &root, + &pid)) + { + goto CleanupAndReturn; + } + + char procname[MAXCOMLEN + 1]; + proc_name(pid, procname, MAXCOMLEN + 1); + + if (fileFlaggedInRoot) + { + int kauthResult; + int kauthError; + if (!TrySendRequestAndWaitForResponse( + root, + MessageType_KtoU_NotifyFileModified, currentVnode, - action, - &root, - &pid)) + pid, + procname, + &kauthResult, + &kauthError)) { goto CleanupAndReturn; } - - char procname[MAXCOMLEN + 1]; - proc_name(pid, procname, MAXCOMLEN + 1); - + } + else + { int kauthResult; int kauthError; if (!TrySendRequestAndWaitForResponse( root, - MessageType_KtoU_NotifyFileModified, + MessageType_KtoU_NotifyFileCreated, currentVnode, pid, procname, @@ -484,7 +524,6 @@ static bool ShouldHandleVnodeOpEvent( } *root = VirtualizationRoots_FindForVnode(vnode); - int16_t rootIndex = nullptr == *root ? -1 : (*root)->index; if (nullptr == *root) { @@ -497,78 +536,8 @@ static bool ShouldHandleVnodeOpEvent( { // There is no registered provider for this root - // TODO(Mac): why do we need to check rootIndex > 0 here? - // If root was null, we would have already exited. And if not null, it can't be < 0. - if (rootIndex >= 0) - { - bool vnodeIsDir = (*vnodeType == VDIR); - - // File/directory is within an offline virtualization root (no provider) - // Allow read-only access to hydrated files, deny any writes except - // deletions, and prevent most read accesses to empty files. - // Empty directories need to be read/searched in order for them to - // be deleted by rm -r - if (ActionBitsNotSet(action, KAUTH_VNODE_ACCESS) && - ActionBitIsSet( - action, - KAUTH_VNODE_WRITE_ATTRIBUTES | - KAUTH_VNODE_WRITE_EXTATTRIBUTES | - KAUTH_VNODE_WRITE_DATA | - KAUTH_VNODE_APPEND_DATA | - KAUTH_VNODE_WRITE_SECURITY | - KAUTH_VNODE_LINKTARGET)) - { - KextLog_FileNote( - vnode, - "ShouldHandleEvent - write action 0x%x by process %u (%s) DENIED on %s with offline provider.", - action, - *pid, - procname, - vnodeIsDir ? "directory" : "file"); - - *kauthResult = KAUTH_RESULT_DENY; - return false; - } - - if (FileFlagsBitIsSet(*vnodeFileFlags, FileFlags_IsEmpty)) - { - // Empty files/directories with offline provider may only be queried or deleted - if (ActionBitIsSet( - action, - KAUTH_VNODE_ACCESS | - KAUTH_VNODE_DELETE_CHILD | - KAUTH_VNODE_DELETE | - KAUTH_VNODE_READ_EXTATTRIBUTES)) - { - *kauthResult = KAUTH_RESULT_DEFER; - return false; - } - - // Empty directories may additionally have their attributes and security read, and contents listed/searched (otherwise rm -r doesn't work) - if (vnodeIsDir && - ActionBitIsSet( - action, - KAUTH_VNODE_READ_ATTRIBUTES | - KAUTH_VNODE_READ_SECURITY | - KAUTH_VNODE_LIST_DIRECTORY | - KAUTH_VNODE_SEARCH)) - { - *kauthResult = KAUTH_RESULT_DEFER; - return false; - } - - // Disallow any other operations on empty placeholders - KextLog_FileNote( - vnode, - "ShouldHandleEvent - action 0x%x by process %u (%s) DENIED on empty %s with offline provider.", - action, - *pid, - procname, - vnodeIsDir ? "directory" : "file"); - *kauthResult = KAUTH_RESULT_DENY; - return false; - } - } + // TODO(Mac): Protect files in the worktree from modification (and prevent + // the creation of new files) when the provider is offline *kauthResult = KAUTH_RESULT_DEFER; return false; @@ -594,22 +563,6 @@ static bool ShouldHandleFileOpEvent( VirtualizationRoot** root, int* pid) { - vtype vnodeType = vnode_vtype(vnode); - if (ShouldIgnoreVnodeType(vnodeType, vnode)) - { - return false; - } - - // TODO(Mac): We will still want to handle renames into a root, and those vnodes would - // not yet have the FileFlags_IsInVirtualizationRoot set - uint32_t vnodeFileFlags = ReadVNodeFileFlags(vnode, context); - if (!FileFlagsBitIsSet(vnodeFileFlags, FileFlags_IsInVirtualizationRoot)) - { - // This vnode is not part of ANY virtualization root, so exit now before doing any more work. - // This gives us a cheap way to avoid adding overhead to IO outside of a virtualization root. - return false; - } - *root = VirtualizationRoots_FindForVnode(vnode); if (nullptr == *root) { @@ -782,20 +735,49 @@ static uint32_t ReadVNodeFileFlags(vnode_t vn, vfs_context_t context) return attributes.va_flags; } -static bool FileFlagsBitIsSet(uint32_t fileFlags, uint32_t bit) +static bool HasAncestorFlaggedAsInRoot(vnode_t vnode, vfs_context_t context) +{ + vnode_get(vnode); + + bool inRoot = false; + + // Search up the tree until we hit a folder know to be inside a virtualization root + // or the root of the file system + while (NULLVP != vnode && !vnode_isvroot(vnode)) + { + uint32_t vnodeFileFlags = ReadVNodeFileFlags(vnode, context); + if (FileFlagsBitIsSet(vnodeFileFlags, FileFlags_IsInVirtualizationRoot)) + { + inRoot = true; + break; + } + vnode_t parent = vnode_getparent(vnode); + vnode_put(vnode); + vnode = parent; + } + + if (NULLVP != vnode) + { + vnode_put(vnode); + } + + return inRoot; +} + +static inline bool FileFlagsBitIsSet(uint32_t fileFlags, uint32_t bit) { // Note: if multiple bits are set in 'bit', this will return true if ANY are set in fileFlags return 0 != (fileFlags & bit); } -static bool ActionBitIsSet(kauth_action_t action, kauth_action_t mask) +static inline bool FileIsFlaggedAsInRoot(vnode_t vnode, vfs_context_t context) { - return action & mask; + uint32_t vnodeFileFlags = ReadVNodeFileFlags(vnode, context); + return FileFlagsBitIsSet(vnodeFileFlags, FileFlags_IsInVirtualizationRoot); } - -static bool ActionBitsNotSet(kauth_action_t action, kauth_action_t mask) +static inline bool ActionBitIsSet(kauth_action_t action, kauth_action_t mask) { - return 0 == (action & mask); + return action & mask; } static bool IsFileSystemCrawler(char* procname) diff --git a/ProjFS.Mac/PrjFSKext/public/Message.h b/ProjFS.Mac/PrjFSKext/public/Message.h index 5817586f2..723303327 100644 --- a/ProjFS.Mac/PrjFSKext/public/Message.h +++ b/ProjFS.Mac/PrjFSKext/public/Message.h @@ -19,6 +19,7 @@ typedef enum MessageType_KtoU_NotifyFileModified, MessageType_KtoU_NotifyFilePreDelete, MessageType_KtoU_NotifyDirectoryPreDelete, + MessageType_KtoU_NotifyFileCreated, // Responses MessageType_Response_Success, diff --git a/ProjFS.Mac/PrjFSLib.Mac.Managed/VirtualizationInstance.cs b/ProjFS.Mac/PrjFSLib.Mac.Managed/VirtualizationInstance.cs index 0fd6b161a..40638e54b 100644 --- a/ProjFS.Mac/PrjFSLib.Mac.Managed/VirtualizationInstance.cs +++ b/ProjFS.Mac/PrjFSLib.Mac.Managed/VirtualizationInstance.cs @@ -15,7 +15,8 @@ public class VirtualizationInstance public virtual GetFileStreamCallback OnGetFileStream { get; set; } public virtual NotifyFileModified OnFileModified { get; set; } - public virtual NotifyPreDeleteEvent OnPreDelete { get; set; } + public virtual NotifyPreDeleteEvent OnPreDelete { get; set; } + public virtual NotifyNewFileCreatedEvent OnNewFileCreated { get; set; } public static Result ConvertDirectoryToVirtualizationRoot(string fullPath) { @@ -141,6 +142,10 @@ private Result OnNotifyOperation( case NotificationType.FileModified: this.OnFileModified(relativePath); return Result.Success; + + case NotificationType.NewFileCreated: + this.OnNewFileCreated(relativePath, isDirectory); + return Result.Success; } return Result.ENotYetImplemented; diff --git a/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp b/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp index 21b13c5d2..9956c0324 100644 --- a/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp +++ b/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp @@ -450,6 +450,20 @@ static void HandleKernelRequest(Message request, void* messageMemory) PrjFS_NotificationType_PreDelete); 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; + } } // async callbacks are not yet implemented From 04a5ef916c41a14a687b56a545ce88c90411c211 Mon Sep 17 00:00:00 2001 From: William Baker Date: Mon, 20 Aug 2018 10:04:00 -0700 Subject: [PATCH 2/4] Update MirrorProvider --- .../MacFileSystemVirtualizer.cs | 8 +++++++- .../WindowsFileSystemVirtualizer.cs | 19 ++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/MirrorProvider/MirrorProvider.Mac/MacFileSystemVirtualizer.cs b/MirrorProvider/MirrorProvider.Mac/MacFileSystemVirtualizer.cs index 3e2567548..5f20521bb 100644 --- a/MirrorProvider/MirrorProvider.Mac/MacFileSystemVirtualizer.cs +++ b/MirrorProvider/MirrorProvider.Mac/MacFileSystemVirtualizer.cs @@ -21,7 +21,8 @@ public override bool TryStartVirtualizationInstance(Enlistment enlistment, out s this.virtualizationInstance.OnEnumerateDirectory = this.OnEnumerateDirectory; this.virtualizationInstance.OnGetFileStream = this.OnGetFileStream; this.virtualizationInstance.OnFileModified = this.OnFileModified; - this.virtualizationInstance.OnPreDelete = this.OnPreDelete; + this.virtualizationInstance.OnPreDelete = this.OnPreDelete; + this.virtualizationInstance.OnNewFileCreated = this.OnNewFileCreated; Result result = this.virtualizationInstance.StartVirtualizationInstance( enlistment.SrcRoot, @@ -157,6 +158,11 @@ private Result OnPreDelete(string relativePath, bool isDirectory) { Console.WriteLine($"OnPreDelete (isDirectory: {isDirectory}): {relativePath}"); return Result.Success; + } + + private void OnNewFileCreated(string relativePath, bool isDirectory) + { + Console.WriteLine($"OnNewFileCreated (isDirectory: {isDirectory}): {relativePath}"); } private static byte[] ToVersionIdByteArray(byte version) diff --git a/MirrorProvider/MirrorProvider.Windows/WindowsFileSystemVirtualizer.cs b/MirrorProvider/MirrorProvider.Windows/WindowsFileSystemVirtualizer.cs index 46956a836..cecea9dce 100644 --- a/MirrorProvider/MirrorProvider.Windows/WindowsFileSystemVirtualizer.cs +++ b/MirrorProvider/MirrorProvider.Windows/WindowsFileSystemVirtualizer.cs @@ -35,13 +35,18 @@ public override bool TryStartVirtualizationInstance(Enlistment enlistment, out s this.virtualizationInstance.OnGetPlaceholderInformation = this.GetPlaceholderInformation; this.virtualizationInstance.OnGetFileStream = this.GetFileStream; this.virtualizationInstance.OnNotifyPreDelete = this.OnPreDelete; + this.virtualizationInstance.OnNotifyNewFileCreated = this.OnNewFileCreated; this.virtualizationInstance.OnNotifyFileHandleClosedFileModifiedOrDeleted = this.OnFileModifiedOrDeleted; uint threadCount = (uint)Environment.ProcessorCount * 2; NotificationMapping[] notificationMappings = new NotificationMapping[] { - new NotificationMapping(NotificationType.PreDelete | NotificationType.FileHandleClosedFileModified, string.Empty), + new NotificationMapping( + NotificationType.NewFileCreated | + NotificationType.PreDelete | + NotificationType.FileHandleClosedFileModified, + string.Empty), }; HResult result = this.virtualizationInstance.StartVirtualizationInstance( @@ -290,6 +295,18 @@ private HResult OnPreDelete(string relativePath, bool isDirectory) return HResult.Ok; } + private void OnNewFileCreated( + string relativePath, + bool isDirectory, + uint desiredAccess, + uint shareMode, + uint createDisposition, + uint createOptions, + ref NotificationType notificationMask) + { + Console.WriteLine($"OnNewFileCreated (isDirectory: {isDirectory}): {relativePath}"); + } + private void OnFileModifiedOrDeleted(string relativePath, bool isDirectory, bool isFileModified, bool isFileDeleted) { // To keep WindowsFileSystemVirtualizer in sync with MacFileSystemVirtualizer we're only registering for From 68f6f69b3b1c399fff4ea5d3607bef9da79849f9 Mon Sep 17 00:00:00 2001 From: William Baker Date: Tue, 21 Aug 2018 11:55:43 -0700 Subject: [PATCH 3/4] PR Feeback: Remove HasAncestorFlaggedAsInRoot and add TODO --- .../PrjFSKext/PrjFSKext/KauthHandler.cpp | 36 ------------------- ProjFS.Mac/PrjFSLib/PrjFSLib.cpp | 2 ++ 2 files changed, 2 insertions(+), 36 deletions(-) diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp index db177bb4a..7dc73f78f 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp @@ -35,7 +35,6 @@ static int HandleFileOpOperation( static int GetPid(vfs_context_t context); static uint32_t ReadVNodeFileFlags(vnode_t vn, vfs_context_t context); -static inline bool HasAncestorFlaggedAsInRoot(vnode_t vnode, 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); @@ -400,11 +399,6 @@ static int HandleFileOpOperation( { goto CleanupAndReturn; } - - if (!fileFlaggedInRoot && !HasAncestorFlaggedAsInRoot(currentVnode, context)) - { - goto CleanupAndReturn; - } VirtualizationRoot* root = nullptr; int pid; @@ -566,7 +560,6 @@ static bool ShouldHandleFileOpEvent( *root = VirtualizationRoots_FindForVnode(vnode); if (nullptr == *root) { - KextLog_FileNote(vnode, "ShouldHandleFileOpEvent(%d): No virtualization root found for file with set flag.", action); return false; } else if (nullptr == (*root)->providerUserClient) @@ -735,35 +728,6 @@ static uint32_t ReadVNodeFileFlags(vnode_t vn, vfs_context_t context) return attributes.va_flags; } -static bool HasAncestorFlaggedAsInRoot(vnode_t vnode, vfs_context_t context) -{ - vnode_get(vnode); - - bool inRoot = false; - - // Search up the tree until we hit a folder know to be inside a virtualization root - // or the root of the file system - while (NULLVP != vnode && !vnode_isvroot(vnode)) - { - uint32_t vnodeFileFlags = ReadVNodeFileFlags(vnode, context); - if (FileFlagsBitIsSet(vnodeFileFlags, FileFlags_IsInVirtualizationRoot)) - { - inRoot = true; - break; - } - vnode_t parent = vnode_getparent(vnode); - vnode_put(vnode); - vnode = parent; - } - - if (NULLVP != vnode) - { - vnode_put(vnode); - } - - return inRoot; -} - static inline bool FileFlagsBitIsSet(uint32_t fileFlags, uint32_t bit) { // Note: if multiple bits are set in 'bit', this will return true if ANY are set in fileFlags diff --git a/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp b/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp index 9956c0324..48c602ced 100644 --- a/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp +++ b/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp @@ -455,6 +455,8 @@ static void HandleKernelRequest(Message request, void* messageMemory) { char fullPath[PrjFSMaxPath]; CombinePaths(s_virtualizationRootFullPath.c_str(), request.path, fullPath); + + // TODO(Mac): Handle SetBitInFileFlags failures SetBitInFileFlags(fullPath, FileFlags_IsInVirtualizationRoot, true); result = HandleFileNotification( From 72033a856659e1d086f7712e0552079ccc23f7cb Mon Sep 17 00:00:00 2001 From: William Baker Date: Tue, 21 Aug 2018 11:57:46 -0700 Subject: [PATCH 4/4] Convert tabs to spaces --- ProjFS.Mac/PrjFSLib/PrjFSLib.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp b/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp index 48c602ced..5e18ffec9 100644 --- a/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp +++ b/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp @@ -456,7 +456,7 @@ static void HandleKernelRequest(Message request, void* messageMemory) char fullPath[PrjFSMaxPath]; CombinePaths(s_virtualizationRootFullPath.c_str(), request.path, fullPath); - // TODO(Mac): Handle SetBitInFileFlags failures + // TODO(Mac): Handle SetBitInFileFlags failures SetBitInFileFlags(fullPath, FileFlags_IsInVirtualizationRoot, true); result = HandleFileNotification(