From cada1004c5da1307bc6d8bfe653cf3f06ac80d73 Mon Sep 17 00:00:00 2001 From: Jessica Schumaker Date: Thu, 14 Feb 2019 16:24:45 -0500 Subject: [PATCH 1/5] Mac: Add PreConvertToFull when Write Access is requested - This will mark the file as D in placeholders.dat and insert into modifiedPaths.dat - This cleans up the existing ModifiedFile event which was also being used as the initial switch from placeholders.dat --- .../Tests/GitCommands/GitRepoTests.cs | 4 ++- .../Tests/GitCommands/StatusTests.cs | 3 +- .../MacFileSystemVirtualizer.cs | 21 +++++------ .../WindowsFileSystemVirtualizer.cs | 16 +-------- .../FileSystem/FileSystemVirtualizer.cs | 18 ++++++++++ .../MacFileSystemVirtualizer.cs | 7 ++++ .../WindowsFileSystemVirtualizer.cs | 7 ++++ ProjFS.Mac/PrjFSKext/KauthHandler.cpp | 35 ++++++++++++++++++- ProjFS.Mac/PrjFSKext/public/Message.h | 1 + .../PrjFSKext/public/PrjFSPerfCounter.h | 1 + .../VirtualizationInstance.cs | 4 +++ ProjFS.Mac/PrjFSLib/PrjFSLib.cpp | 10 +++--- .../PrjFSLib/prjfs-log/kext-perf-tracing.cpp | 1 + 13 files changed, 92 insertions(+), 36 deletions(-) diff --git a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs index bf933da9d5..83c4f052a6 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitRepoTests.cs @@ -243,10 +243,12 @@ protected void CreateFileWithoutClose(string path) this.FileSystem.CreateFileWithoutClose(controlFile); } - protected void OpenFileAndWriteWithoutClose(string path, string contents) + protected void ReadFileAndWriteWithoutClose(string path, string contents) { string virtualFile = Path.Combine(this.Enlistment.RepoRoot, path); string controlFile = Path.Combine(this.ControlGitRepo.RootPath, path); + this.FileSystem.ReadAllText(virtualFile); + this.FileSystem.ReadAllText(controlFile); this.FileSystem.OpenFileAndWriteWithoutClose(virtualFile, contents); this.FileSystem.OpenFileAndWriteWithoutClose(controlFile, contents); } diff --git a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/StatusTests.cs b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/StatusTests.cs index 6357cf2f51..0928f1c922 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/StatusTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/StatusTests.cs @@ -46,12 +46,11 @@ public void CreateFileWithoutClose() this.ValidateGitCommand("status"); } - [Ignore("This test exposes a bug we don't yet have a fix for")] [TestCase] public void WriteWithoutClose() { string srcPath = @"Readme.md"; - this.OpenFileAndWriteWithoutClose(srcPath, "More Stuff"); + this.ReadFileAndWriteWithoutClose(srcPath, "More Stuff"); this.ValidateGitCommand("status"); } diff --git a/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs b/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs index 0c2315fbcf..aba63ebd3a 100644 --- a/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs +++ b/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs @@ -217,7 +217,8 @@ 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; + this.virtualizationInstance.OnHardLinkCreated = this.OnHardLinkCreated; + this.virtualizationInstance.OnFilePreConvertToFull = this.NotifyFilePreConvertToFull; uint threadCount = (uint)Environment.ProcessorCount * 2; @@ -437,24 +438,18 @@ private void OnFileModified(string relativePath) { this.OnDotGitFileOrFolderChanged(relativePath); } - else - { - // TODO(Mac): As a temporary work around (until we have a ConvertToFull type notification) treat every modification - // as the first write to the file - bool isFolder; - string fileName; - bool isPathProjected = this.FileSystemCallbacks.GitIndexProjection.IsPathProjected(relativePath, out fileName, out isFolder); - if (isPathProjected) - { - this.FileSystemCallbacks.OnFileConvertedToFull(relativePath); - } - } } catch (Exception e) { this.LogUnhandledExceptionAndExit(nameof(this.OnFileModified), this.CreateEventMetadata(relativePath, e)); } } + + private Result NotifyFilePreConvertToFull(string relativePath) + { + this.OnFilePreConvertToFull(relativePath); + return Result.Success; + } private Result OnPreDelete(string relativePath, bool isDirectory) { diff --git a/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs b/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs index b06cbc2b2b..f3b6704bbe 100644 --- a/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs +++ b/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs @@ -1264,21 +1264,7 @@ private void NotifyFileHandleClosedFileModifiedOrDeletedHandler( private HResult NotifyFilePreConvertToFullHandler(string virtualPath) { - try - { - bool isFolder; - string fileName; - bool isPathProjected = this.FileSystemCallbacks.GitIndexProjection.IsPathProjected(virtualPath, out fileName, out isFolder); - if (isPathProjected) - { - this.FileSystemCallbacks.OnFileConvertedToFull(virtualPath); - } - } - catch (Exception e) - { - this.LogUnhandledExceptionAndExit(nameof(this.NotifyFilePreConvertToFullHandler), this.CreateEventMetadata(virtualPath, e)); - } - + this.OnFilePreConvertToFull(virtualPath); return HResult.Ok; } diff --git a/GVFS/GVFS.Virtualization/FileSystem/FileSystemVirtualizer.cs b/GVFS/GVFS.Virtualization/FileSystem/FileSystemVirtualizer.cs index c95eda68ad..30d5103379 100644 --- a/GVFS/GVFS.Virtualization/FileSystem/FileSystemVirtualizer.cs +++ b/GVFS/GVFS.Virtualization/FileSystem/FileSystemVirtualizer.cs @@ -277,6 +277,24 @@ protected void OnHardLinkCreated(string relativeExistingFilePath, string relativ } } + protected void OnFilePreConvertToFull(string relativePath) + { + try + { + bool isFolder; + string fileName; + bool isPathProjected = this.FileSystemCallbacks.GitIndexProjection.IsPathProjected(relativePath, out fileName, out isFolder); + if (isPathProjected) + { + this.FileSystemCallbacks.OnFileConvertedToFull(relativePath); + } + } + catch (Exception e) + { + this.LogUnhandledExceptionAndExit(nameof(this.OnFilePreConvertToFull), this.CreateEventMetadata(relativePath, e)); + } + } + protected EventMetadata CreateEventMetadata( Guid enumerationId, string relativePath = null, diff --git a/MirrorProvider/MirrorProvider.Mac/MacFileSystemVirtualizer.cs b/MirrorProvider/MirrorProvider.Mac/MacFileSystemVirtualizer.cs index b8df167896..b021d93045 100644 --- a/MirrorProvider/MirrorProvider.Mac/MacFileSystemVirtualizer.cs +++ b/MirrorProvider/MirrorProvider.Mac/MacFileSystemVirtualizer.cs @@ -27,6 +27,7 @@ public override bool TryStartVirtualizationInstance(Enlistment enlistment, out s this.virtualizationInstance.OnNewFileCreated = this.OnNewFileCreated; this.virtualizationInstance.OnFileRenamed = this.OnFileRenamed; this.virtualizationInstance.OnHardLinkCreated = this.OnHardLinkCreated; + this.virtualizationInstance.OnFilePreConvertToFull = this.OnFilePreConvertToFull; Result result = this.virtualizationInstance.StartVirtualizationInstance( enlistment.SrcRoot, @@ -201,6 +202,12 @@ private void OnHardLinkCreated(string relativeNewLinkPath) Console.WriteLine($"OnHardLinkCreated: {relativeNewLinkPath}"); } + private Result OnFilePreConvertToFull(string relativePath) + { + Console.WriteLine($"OnFilePreConvertToFull: {relativePath}"); + return Result.Success; + } + private bool TryGetSymLinkTarget(string relativePath, out string symLinkTarget) { symLinkTarget = null; diff --git a/MirrorProvider/MirrorProvider.Windows/WindowsFileSystemVirtualizer.cs b/MirrorProvider/MirrorProvider.Windows/WindowsFileSystemVirtualizer.cs index aebdcd7baf..137b4b740a 100644 --- a/MirrorProvider/MirrorProvider.Windows/WindowsFileSystemVirtualizer.cs +++ b/MirrorProvider/MirrorProvider.Windows/WindowsFileSystemVirtualizer.cs @@ -39,6 +39,7 @@ public override bool TryStartVirtualizationInstance(Enlistment enlistment, out s this.virtualizationInstance.OnNotifyFileHandleClosedFileModifiedOrDeleted = this.OnFileModifiedOrDeleted; this.virtualizationInstance.OnNotifyFileRenamed = this.OnFileRenamed; this.virtualizationInstance.OnNotifyHardlinkCreated = this.OnHardlinkCreated; + this.virtualizationInstance.OnNotifyFilePreConvertToFull = this.OnFilePreConvertToFull; uint threadCount = (uint)Environment.ProcessorCount * 2; @@ -336,6 +337,12 @@ private void OnHardlinkCreated( Console.WriteLine($"OnHardlinkCreated, relativeExistingFilePath: {relativeExistingFilePath}, relativeNewLinkFilePath: {relativeNewLinkFilePath}"); } + private HResult OnFilePreConvertToFull(string relativePath) + { + Console.WriteLine($"OnFilePreConvertToFullHandler: {relativePath}"); + return HResult.Ok; + } + // TODO: Add this to the ProjFS API private static HResult HResultFromWin32(int win32error) { diff --git a/ProjFS.Mac/PrjFSKext/KauthHandler.cpp b/ProjFS.Mac/PrjFSKext/KauthHandler.cpp index 2c30e5e44e..ff9d83e3c2 100644 --- a/ProjFS.Mac/PrjFSKext/KauthHandler.cpp +++ b/ProjFS.Mac/PrjFSKext/KauthHandler.cpp @@ -17,6 +17,7 @@ #include "kernel-header-wrappers/mount.h" #include "KextLog.hpp" #include "ProviderMessaging.hpp" +#include "public/PrjFSXattrs.h" #ifdef KEXT_UNIT_TESTING #include "KauthHandlerTestable.hpp" @@ -383,6 +384,38 @@ static int HandleVnodeOperation( goto CleanupAndReturn; } } + + if (ActionBitIsSet(action, KAUTH_VNODE_WRITE_DATA)) + { + if (!TryGetVirtualizationRoot(&perfTracer, context, currentVnode, pid, CallbackPolicy_UserInitiatedOnly, &root, &vnodeFsidInode, &kauthResult, kauthError)) + { + goto CleanupAndReturn; + } + + PrjFSFileXAttrData rootXattr = {}; + SizeOrError xattrResult = Vnode_ReadXattr(currentVnode, PrjFSFileXAttrName, &rootXattr, sizeof(rootXattr)); + if (xattrResult.error == ENOATTR) + { + // Only notify if the file is still a placeholder + goto CleanupAndReturn; + } + + PerfSample preConvertToFullSample(&perfTracer, PrjFSPerfCounter_VnodeOp_PreConvertToFull); + + if (!ProviderMessaging_TrySendRequestAndWaitForResponse( + root, + MessageType_KtoU_NotifyFilePreConvertToFull, + currentVnode, + vnodeFsidInode, + nullptr, // path not needed, use fsid/inode, + pid, + procname, + &kauthResult, + kauthError)) + { + goto CleanupAndReturn; + } + } } } @@ -593,7 +626,7 @@ static int HandleFileOpOperation( { goto CleanupAndReturn; } - + char procname[MAXCOMLEN + 1]; proc_name(pid, procname, MAXCOMLEN + 1); PerfSample fileModifiedSample(&perfTracer, PrjFSPerfCounter_FileOp_FileModified); diff --git a/ProjFS.Mac/PrjFSKext/public/Message.h b/ProjFS.Mac/PrjFSKext/public/Message.h index 7050f72130..e716747316 100644 --- a/ProjFS.Mac/PrjFSKext/public/Message.h +++ b/ProjFS.Mac/PrjFSKext/public/Message.h @@ -21,6 +21,7 @@ typedef enum MessageType_KtoU_NotifyFileRenamed, MessageType_KtoU_NotifyDirectoryRenamed, MessageType_KtoU_NotifyFileHardLinkCreated, + MessageType_KtoU_NotifyFilePreConvertToFull, // Responses MessageType_Response_Success, diff --git a/ProjFS.Mac/PrjFSKext/public/PrjFSPerfCounter.h b/ProjFS.Mac/PrjFSKext/public/PrjFSPerfCounter.h index fff70e8f76..353d0a57a5 100644 --- a/ProjFS.Mac/PrjFSKext/public/PrjFSPerfCounter.h +++ b/ProjFS.Mac/PrjFSKext/public/PrjFSPerfCounter.h @@ -29,6 +29,7 @@ enum PrjFSPerfCounter : int32_t PrjFSPerfCounter_VnodeOp_EnumerateDirectory, PrjFSPerfCounter_VnodeOp_RecursivelyEnumerateDirectory, PrjFSPerfCounter_VnodeOp_HydrateFile, + PrjFSPerfCounter_VnodeOp_PreConvertToFull, PrjFSPerfCounter_FileOp, PrjFSPerfCounter_FileOp_ShouldHandle, diff --git a/ProjFS.Mac/PrjFSLib.Mac.Managed/VirtualizationInstance.cs b/ProjFS.Mac/PrjFSLib.Mac.Managed/VirtualizationInstance.cs index 97b22f8200..4b1cd21f99 100644 --- a/ProjFS.Mac/PrjFSLib.Mac.Managed/VirtualizationInstance.cs +++ b/ProjFS.Mac/PrjFSLib.Mac.Managed/VirtualizationInstance.cs @@ -15,6 +15,7 @@ public class VirtualizationInstance public virtual GetFileStreamCallback OnGetFileStream { get; set; } public virtual NotifyFileModified OnFileModified { get; set; } + public virtual NotifyFilePreConvertToFullEvent OnFilePreConvertToFull { get; set; } public virtual NotifyPreDeleteEvent OnPreDelete { get; set; } public virtual NotifyNewFileCreatedEvent OnNewFileCreated { get; set; } public virtual NotifyFileRenamedEvent OnFileRenamed { get; set; } @@ -204,6 +205,9 @@ private Result OnNotifyOperation( case NotificationType.HardLinkCreated: this.OnHardLinkCreated(relativePath); return Result.Success; + + case NotificationType.PreConvertToFull: + return this.OnFilePreConvertToFull(relativePath); } return Result.ENotYetImplemented; diff --git a/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp b/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp index 40447edd1f..33693c423a 100644 --- a/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp +++ b/ProjFS.Mac/PrjFSLib/PrjFSLib.cpp @@ -701,6 +701,7 @@ static void HandleKernelRequest(void* messageMemory, uint32_t messageSize) case MessageType_KtoU_NotifyFileModified: case MessageType_KtoU_NotifyFilePreDelete: case MessageType_KtoU_NotifyDirectoryPreDelete: + case MessageType_KtoU_NotifyFilePreConvertToFull: { char fullPath[PrjFSMaxPath]; CombinePaths(s_virtualizationRootFullPath.c_str(), request.path, fullPath); @@ -1011,7 +1012,7 @@ static PrjFS_Result HandleFileNotification( #endif PrjFSFileXAttrData xattrData = {}; - bool partialFile = TryGetXAttr(fullPath, PrjFSFileXAttrName, sizeof(PrjFSFileXAttrData), &xattrData); + bool placeholderFile = TryGetXAttr(fullPath, PrjFSFileXAttrName, sizeof(PrjFSFileXAttrData), &xattrData); PrjFS_Result result = s_callbacks.NotifyOperation( 0 /* commandId */, @@ -1024,10 +1025,8 @@ static PrjFS_Result HandleFileNotification( notificationType, nullptr /* destinationRelativePath */); - if (partialFile && PrjFS_NotificationType_FileModified == notificationType) + if (result == 0 && placeholderFile && PrjFS_NotificationType_PreConvertToFull == notificationType) { - // PrjFS_NotificationType_FileModified is a post-modified FileOp event (that cannot be stopped - // by the provider) and so there's no need to check the result of the call to NotifyOperation errno_t result = RemoveXAttrWithoutFollowingLinks(fullPath, PrjFSFileXAttrName); if (0 != result) { @@ -1216,6 +1215,9 @@ static inline PrjFS_NotificationType KUMessageTypeToNotificationType(MessageType case MessageType_KtoU_NotifyFilePreDelete: case MessageType_KtoU_NotifyDirectoryPreDelete: return PrjFS_NotificationType_PreDelete; + + case MessageType_KtoU_NotifyFilePreConvertToFull: + return PrjFS_NotificationType_PreConvertToFull; case MessageType_KtoU_NotifyFileCreated: return PrjFS_NotificationType_NewFileCreated; diff --git a/ProjFS.Mac/PrjFSLib/prjfs-log/kext-perf-tracing.cpp b/ProjFS.Mac/PrjFSLib/prjfs-log/kext-perf-tracing.cpp index bfa8fb26e4..1decd7239a 100644 --- a/ProjFS.Mac/PrjFSLib/prjfs-log/kext-perf-tracing.cpp +++ b/ProjFS.Mac/PrjFSLib/prjfs-log/kext-perf-tracing.cpp @@ -52,6 +52,7 @@ static constexpr const char* const PerfCounterNames[PrjFSPerfCounter_Count] = [PrjFSPerfCounter_VnodeOp_EnumerateDirectory] = " |--RaiseEnumerateDirectoryEvent", [PrjFSPerfCounter_VnodeOp_RecursivelyEnumerateDirectory] = " |--RaiseRecursivelyEnumerateEvent", [PrjFSPerfCounter_VnodeOp_HydrateFile] = " |--RaiseHydrateFileEvent", + [PrjFSPerfCounter_VnodeOp_PreConvertToFull] = " |--RaisePreConvertToFull", [PrjFSPerfCounter_FileOp] = "HandleFileOpOperation", [PrjFSPerfCounter_FileOp_ShouldHandle] = " |--ShouldHandleFileOpEvent", [PrjFSPerfCounter_FileOp_ShouldHandle_FindVirtualizationRoot] = " | |--FindVirtualizationRoot", From 960232015414d956f018f33921cb49498fbbaa1b Mon Sep 17 00:00:00 2001 From: Jessica Schumaker Date: Tue, 19 Mar 2019 10:27:57 -0400 Subject: [PATCH 2/5] Add Tests for HandleVnodeOperation Workaround for kext testing MockCalls::RecordFunctionCall issues Additional Vnode Tests Moved to a new file --- ProjFS.Mac/PrjFS.xcodeproj/project.pbxproj | 7 +- ProjFS.Mac/PrjFSKext/KauthHandler.cpp | 4 +- ProjFS.Mac/PrjFSKext/KauthHandlerTestable.hpp | 8 + ProjFS.Mac/PrjFSKext/ProviderMessaging.cpp | 2 + .../HandleVnodeOperationTests.mm | 152 ++++++++++++++++++ .../PrjFSKextTests/KauthHandlerTests.mm | 1 + .../PrjFSKextTests/KextMockUtilities.hpp | 16 ++ ProjFS.Mac/PrjFSKextTests/MockProc.cpp | 40 +++++ ProjFS.Mac/PrjFSKextTests/MockProc.hpp | 8 + .../PrjFSKextTests/MockVnodeAndMount.cpp | 5 + .../PrjFSKextTests/MockVnodeAndMount.hpp | 1 + 11 files changed, 239 insertions(+), 5 deletions(-) create mode 100644 ProjFS.Mac/PrjFSKextTests/HandleVnodeOperationTests.mm diff --git a/ProjFS.Mac/PrjFS.xcodeproj/project.pbxproj b/ProjFS.Mac/PrjFS.xcodeproj/project.pbxproj index 75dbb9b4a0..03a11fd28c 100644 --- a/ProjFS.Mac/PrjFS.xcodeproj/project.pbxproj +++ b/ProjFS.Mac/PrjFS.xcodeproj/project.pbxproj @@ -80,6 +80,7 @@ F5554F422239883B00B31D19 /* MockProc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F5554F412239883B00B31D19 /* MockProc.cpp */; }; F5E39C7A21F1118D006D65C2 /* KauthHandlerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F5E39C7921F1118D006D65C2 /* KauthHandlerTests.mm */; }; F5E39C8321F11556006D65C2 /* KauthHandlerTestable.hpp in Headers */ = {isa = PBXBuildFile; fileRef = F5E39C8121F11556006D65C2 /* KauthHandlerTestable.hpp */; }; + F5EACDC22242AB2D00EEA70E /* HandleVnodeOperationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F5EACDC12242AB2D00EEA70E /* HandleVnodeOperationTests.mm */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -208,6 +209,7 @@ F5E39C7921F1118D006D65C2 /* KauthHandlerTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = KauthHandlerTests.mm; sourceTree = ""; }; F5E39C7B21F1118D006D65C2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F5E39C8121F11556006D65C2 /* KauthHandlerTestable.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KauthHandlerTestable.hpp; sourceTree = ""; }; + F5EACDC12242AB2D00EEA70E /* HandleVnodeOperationTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = HandleVnodeOperationTests.mm; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -376,14 +378,11 @@ children = ( F5E39C7B21F1118D006D65C2 /* Info.plist */, F5E39C7921F1118D006D65C2 /* KauthHandlerTests.mm */, - 4A781DAF2223391A00DB7733 /* KextLogMock.cpp */, - F557623E22009F0E005DE35E /* KextLogMock.h */, 4A781DAA222330F700DB7733 /* KextMockUtilities.cpp */, 4A781DA92223305C00DB7733 /* KextMockUtilities.hpp */, D9C087F122384670009C1110 /* MemoryTests.mm */, 4A781DAC2223374200DB7733 /* MockVnodeAndMount.cpp */, 4A781DAD2223374200DB7733 /* MockVnodeAndMount.hpp */, - 4A8C13AA21F268FE00002878 /* PrjFSKextTests.exp */, 4A781DB122233A1E00DB7733 /* TestLocks.cpp */, 4A781DB322233A4500DB7733 /* TestMemory.cpp */, 4A781DA42220946000DB7733 /* VirtualizationRootsTests.mm */, @@ -392,6 +391,7 @@ 4A781DAF2223391A00DB7733 /* KextLogMock.cpp */, F5554F402239881800B31D19 /* MockProc.hpp */, F5554F412239883B00B31D19 /* MockProc.cpp */, + F5EACDC12242AB2D00EEA70E /* HandleVnodeOperationTests.mm */, ); path = PrjFSKextTests; sourceTree = ""; @@ -703,6 +703,7 @@ F5554F422239883B00B31D19 /* MockProc.cpp in Sources */, 4A781DB422233A4500DB7733 /* TestMemory.cpp in Sources */, D9C087F222384670009C1110 /* MemoryTests.mm in Sources */, + F5EACDC22242AB2D00EEA70E /* HandleVnodeOperationTests.mm in Sources */, F5E39C7A21F1118D006D65C2 /* KauthHandlerTests.mm in Sources */, 4A781DB222233A1E00DB7733 /* TestLocks.cpp in Sources */, 4A781DB02223391A00DB7733 /* KextLogMock.cpp in Sources */, diff --git a/ProjFS.Mac/PrjFSKext/KauthHandler.cpp b/ProjFS.Mac/PrjFSKext/KauthHandler.cpp index 2638227eee..cc78ad13df 100644 --- a/ProjFS.Mac/PrjFSKext/KauthHandler.cpp +++ b/ProjFS.Mac/PrjFSKext/KauthHandler.cpp @@ -30,7 +30,7 @@ enum ProviderCallbackPolicy }; // Function prototypes -static int HandleVnodeOperation( +KEXT_STATIC int HandleVnodeOperation( kauth_cred_t credential, void* idata, kauth_action_t action, @@ -207,7 +207,7 @@ static void UseMainForkIfNamedStream( } // Private functions -static int HandleVnodeOperation( +KEXT_STATIC int HandleVnodeOperation( kauth_cred_t credential, void* idata, kauth_action_t action, diff --git a/ProjFS.Mac/PrjFSKext/KauthHandlerTestable.hpp b/ProjFS.Mac/PrjFSKext/KauthHandlerTestable.hpp index 5d623f9837..0415f66cd1 100644 --- a/ProjFS.Mac/PrjFSKext/KauthHandlerTestable.hpp +++ b/ProjFS.Mac/PrjFSKext/KauthHandlerTestable.hpp @@ -16,6 +16,14 @@ KEXT_STATIC_INLINE bool ActionBitIsSet(kauth_action_t action, kauth_action_t mas KEXT_STATIC_INLINE bool TryGetFileIsFlaggedAsInRoot(vnode_t vnode, vfs_context_t context, bool* flaggedInRoot); KEXT_STATIC bool IsFileSystemCrawler(const char* procname); KEXT_STATIC bool ShouldIgnoreVnodeType(vtype vnodeType, vnode_t vnode); +KEXT_STATIC int HandleVnodeOperation( + kauth_cred_t credential, + void* idata, + kauth_action_t action, + uintptr_t arg0, + uintptr_t arg1, + uintptr_t arg2, + uintptr_t arg3); KEXT_STATIC bool ShouldHandleVnodeOpEvent( // In params: PerfTracer* perfTracer, diff --git a/ProjFS.Mac/PrjFSKext/ProviderMessaging.cpp b/ProjFS.Mac/PrjFSKext/ProviderMessaging.cpp index ddfc47d649..17ead9e3e6 100644 --- a/ProjFS.Mac/PrjFSKext/ProviderMessaging.cpp +++ b/ProjFS.Mac/PrjFSKext/ProviderMessaging.cpp @@ -124,6 +124,7 @@ void ProviderMessaging_AbortOutstandingEventsForProvider(VirtualizationRootHandl Mutex_Release(s_outstandingMessagesMutex); } +#ifndef KEXT_UNIT_TESTING bool ProviderMessaging_TrySendRequestAndWaitForResponse( VirtualizationRootHandle root, MessageType messageType, @@ -215,6 +216,7 @@ bool ProviderMessaging_TrySendRequestAndWaitForResponse( return result; } +#endif void ProviderMessaging_AbortAllOutstandingEvents() { diff --git a/ProjFS.Mac/PrjFSKextTests/HandleVnodeOperationTests.mm b/ProjFS.Mac/PrjFSKextTests/HandleVnodeOperationTests.mm new file mode 100644 index 0000000000..a19c832ece --- /dev/null +++ b/ProjFS.Mac/PrjFSKextTests/HandleVnodeOperationTests.mm @@ -0,0 +1,152 @@ +#include "../PrjFSKext/kernel-header-wrappers/vnode.h" +#include "../PrjFSKext/KauthHandlerTestable.hpp" +#include "../PrjFSKext/VirtualizationRoots.hpp" +#include "../PrjFSKext/PrjFSProviderUserClient.hpp" +#include "../PrjFSKext/VirtualizationRootsTestable.hpp" +#include "../PrjFSKext/PerformanceTracing.hpp" +#include "../PrjFSKext/public/Message.h" +#include "../PrjFSKext/ProviderMessaging.hpp" +#include "../PrjFSKext/public/PrjFSXattrs.h" +#import +#import +#include "KextMockUtilities.hpp" +#include "MockVnodeAndMount.hpp" +#include "MockProc.hpp" + +using std::shared_ptr; + +class PrjFSProviderUserClient +{ +}; + +MessageType expectedMessageType[3]; +int callCount = 0; +bool ProviderMessaging_TrySendRequestAndWaitForResponse( + VirtualizationRootHandle root, + MessageType messageType, + const vnode_t vnode, + const FsidInode& vnodeFsidInode, + const char* vnodePath, + int pid, + const char* procname, + int* kauthResult, + int* kauthError) +{ + assert(expectedMessageType[callCount] = messageType); + callCount++; + + MockCalls::RecordFunctionCall( + ProviderMessaging_TrySendRequestAndWaitForResponse, + root, + messageType, + vnode, + vnodeFsidInode, + vnodePath, + pid, + procname, + kauthResult, + kauthError); + + return true; +} + +@interface HandleVnodeOperationTests : XCTestCase +@end + +@implementation HandleVnodeOperationTests + +- (void) tearDown +{ + MockVnodes_CheckAndClear(); +} + +- (void)testHandleVnodeOpEvent { + // Setup + kern_return_t initResult = VirtualizationRoots_Init(); + XCTAssertEqual(initResult, KERN_SUCCESS); + + // Parameters + const char* repoPath = "/Users/test/code/Repo"; + const char* filePath = "/Users/test/code/Repo/file"; + const char* dirPath = "/Users/test/code/Repo/dir"; + vfs_context_t _Nonnull context = vfs_context_create(NULL); + //PerfTracer perfTracer; + PrjFSProviderUserClient dummyClient; + pid_t dummyClientPid=100; + + // Create Vnode Tree + shared_ptr testMount = mount::Create(); + shared_ptr repoRootVnode = testMount->CreateVnodeTree(repoPath, VDIR); + shared_ptr testFileVnode = testMount->CreateVnodeTree(filePath); + shared_ptr testDirVnode = testMount->CreateVnodeTree(dirPath, VDIR); + + // Register provider for the repository path (Simulate a mount) + VirtualizationRootResult result = VirtualizationRoot_RegisterProviderForPath(&dummyClient, dummyClientPid, repoPath); + XCTAssertEqual(result.error, 0); + vnode_put(s_virtualizationRoots[result.root].rootVNode); + + // Read a file that has not been hydrated yet + expectedMessageType[0] = MessageType_KtoU_HydrateFile; + testFileVnode->attrValues.va_flags = FileFlags_IsEmpty | FileFlags_IsInVirtualizationRoot; + HandleVnodeOperation( + nullptr, + nullptr, + KAUTH_VNODE_READ_DATA, + reinterpret_cast(context), + reinterpret_cast(testFileVnode.get()), + 0, + 0); + XCTAssertTrue(MockCalls::DidCallFunction(ProviderMessaging_TrySendRequestAndWaitForResponse)); + MockCalls::Clear(); + callCount = 0; + + // Ensure a file without an IsEmpty tag is not hydrated + testFileVnode->attrValues.va_flags = FileFlags_IsInVirtualizationRoot; + HandleVnodeOperation( + nullptr, + nullptr, + KAUTH_VNODE_READ_DATA, + reinterpret_cast(context), + reinterpret_cast(testFileVnode.get()), + 0, + 0); + XCTAssertFalse(MockCalls::DidCallFunction(ProviderMessaging_TrySendRequestAndWaitForResponse)); + MockCalls::Clear(); + + // Ensure a file is Deleted + expectedMessageType[0] = MessageType_KtoU_NotifyFilePreDelete; + testFileVnode->attrValues.va_flags = FileFlags_IsInVirtualizationRoot; + HandleVnodeOperation( + nullptr, + nullptr, + KAUTH_VNODE_DELETE, + reinterpret_cast(context), + reinterpret_cast(testFileVnode.get()), + 0, + 0); + XCTAssertTrue(MockCalls::DidCallFunction(ProviderMessaging_TrySendRequestAndWaitForResponse)); + MockCalls::Clear(); + callCount = 0; + + // Ensure a directory is Deleted + expectedMessageType[0] = MessageType_KtoU_NotifyDirectoryPreDelete; + expectedMessageType[1] = MessageType_KtoU_RecursivelyEnumerateDirectory; + testDirVnode->attrValues.va_flags = FileFlags_IsInVirtualizationRoot; + HandleVnodeOperation( + nullptr, + nullptr, + KAUTH_VNODE_DELETE, + reinterpret_cast(context), + reinterpret_cast(testDirVnode.get()), + 0, + 0); + XCTAssertTrue(MockCalls::DidCallFunction(ProviderMessaging_TrySendRequestAndWaitForResponse)); + MockCalls::Clear(); + callCount = 0; + + // Teardown + VirtualizationRoots_Cleanup(); + vfs_context_rele(context); +} + +@end diff --git a/ProjFS.Mac/PrjFSKextTests/KauthHandlerTests.mm b/ProjFS.Mac/PrjFSKextTests/KauthHandlerTests.mm index 1b8dfc3690..da77361b24 100644 --- a/ProjFS.Mac/PrjFSKextTests/KauthHandlerTests.mm +++ b/ProjFS.Mac/PrjFSKextTests/KauthHandlerTests.mm @@ -8,6 +8,7 @@ using std::shared_ptr; + @interface KauthHandlerTests : XCTestCase @end diff --git a/ProjFS.Mac/PrjFSKextTests/KextMockUtilities.hpp b/ProjFS.Mac/PrjFSKextTests/KextMockUtilities.hpp index c33f57a61e..ea43976ece 100644 --- a/ProjFS.Mac/PrjFSKextTests/KextMockUtilities.hpp +++ b/ProjFS.Mac/PrjFSKextTests/KextMockUtilities.hpp @@ -68,6 +68,22 @@ class MockCalls SpecificFunctionCallRecorder::functionTypeRegister.RecordFunctionCall(fn, singleton.nextCallSequenceNumber++); } + template + static void RecordFunctionCall(R (*fn)(ARGS...)) + { + singleton.functionTypeCallRecorders.insert(&SpecificFunctionCallRecorder::functionTypeRegister); + + SpecificFunctionCallRecorder::functionTypeRegister.RecordFunctionCall(fn, singleton.nextCallSequenceNumber++); + } + + template + static void RecordFunctionCall(R (*fn)(ARGS...), ACTUAL_ARGS... args) + { + singleton.functionTypeCallRecorders.insert(&SpecificFunctionCallRecorder::functionTypeRegister); + + SpecificFunctionCallRecorder::functionTypeRegister.RecordFunctionCall(fn, singleton.nextCallSequenceNumber++); + } + static void Clear(); template diff --git a/ProjFS.Mac/PrjFSKextTests/MockProc.cpp b/ProjFS.Mac/PrjFSKextTests/MockProc.cpp index 76614ccf03..e2d0bb86d6 100644 --- a/ProjFS.Mac/PrjFSKextTests/MockProc.cpp +++ b/ProjFS.Mac/PrjFSKextTests/MockProc.cpp @@ -22,3 +22,43 @@ proc_t vfs_context_proc(vfs_context_t ctx) { return NULL; } + +proc_t proc_self(void) +{ + return NULL; +} + +kauth_cred_t kauth_cred_proc_ref(proc_t procp) +{ + return NULL; +} + +uid_t kauth_cred_getuid(kauth_cred_t _cred) +{ + // Values over 500 are non-system processes + return 501; +} + +void kauth_cred_unref(kauth_cred_t *_cred) +{ +} + +int proc_ppid(proc_t) +{ + return 1; +} + +proc_t proc_find(int pid) +{ + return NULL; +} + +int proc_rele(proc_t p) +{ + return 1; +} + +int proc_selfpid(void) +{ + return 1; +} diff --git a/ProjFS.Mac/PrjFSKextTests/MockProc.hpp b/ProjFS.Mac/PrjFSKextTests/MockProc.hpp index f3dcd6e163..14ae13d3c2 100644 --- a/ProjFS.Mac/PrjFSKextTests/MockProc.hpp +++ b/ProjFS.Mac/PrjFSKextTests/MockProc.hpp @@ -14,6 +14,14 @@ extern "C" int proc_pid(proc_t); void proc_name(int pid, char* buf, int size); proc_t vfs_context_proc(vfs_context_t ctx); + proc_t proc_self(void); + kauth_cred_t kauth_cred_proc_ref(proc_t procp); + uid_t kauth_cred_getuid(kauth_cred_t _cred); + void kauth_cred_unref(kauth_cred_t *_cred); + int proc_ppid(proc_t); + proc_t proc_find(int pid); + int proc_rele(proc_t p); + int proc_selfpid(void); } void SetProcName(const std::string& procName); diff --git a/ProjFS.Mac/PrjFSKextTests/MockVnodeAndMount.cpp b/ProjFS.Mac/PrjFSKextTests/MockVnodeAndMount.cpp index 6a1e13161c..0f9ef3dd51 100644 --- a/ProjFS.Mac/PrjFSKextTests/MockVnodeAndMount.cpp +++ b/ProjFS.Mac/PrjFSKextTests/MockVnodeAndMount.cpp @@ -381,3 +381,8 @@ int vnode_isvroot(vnode_t vnode) { return vnode->GetMountPoint()->GetRootVnode().get() == vnode; } + +int vnode_isnamedstream(vnode_t vp) +{ + return 0; +} diff --git a/ProjFS.Mac/PrjFSKextTests/MockVnodeAndMount.hpp b/ProjFS.Mac/PrjFSKextTests/MockVnodeAndMount.hpp index 37a250d0b8..951a5379f3 100644 --- a/ProjFS.Mac/PrjFSKextTests/MockVnodeAndMount.hpp +++ b/ProjFS.Mac/PrjFSKextTests/MockVnodeAndMount.hpp @@ -138,6 +138,7 @@ struct vnode friend struct mount; friend int vnode_getattr(vnode_t vp, struct vnode_attr* vap, vfs_context_t ctx); friend int vn_getpath(vnode_t vnode, char* pathBuffer, int* pathLengthInOut); + friend int vnode_isnamedstream(vnode_t vp); }; From f6cc7c1f99e68296173ff76a8680a575f4abecd9 Mon Sep 17 00:00:00 2001 From: Jessica Schumaker Date: Thu, 21 Mar 2019 09:31:05 -0400 Subject: [PATCH 3/5] IN PROGRESS --- ProjFS.Mac/PrjFS.xcodeproj/project.pbxproj | 33 +++++++ .../HandleVnodeOperationTests.mm | 98 ++++++++++++------- 2 files changed, 97 insertions(+), 34 deletions(-) diff --git a/ProjFS.Mac/PrjFS.xcodeproj/project.pbxproj b/ProjFS.Mac/PrjFS.xcodeproj/project.pbxproj index 03a11fd28c..8da22b36fa 100644 --- a/ProjFS.Mac/PrjFS.xcodeproj/project.pbxproj +++ b/ProjFS.Mac/PrjFS.xcodeproj/project.pbxproj @@ -807,6 +807,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_STATIC_ANALYZER_MODE = deep; + CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = UBF8T346G9; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -820,6 +821,7 @@ MODULE_VERSION = 1.0.0d1; PRODUCT_BUNDLE_IDENTIFIER = org.vfsforgit.PrjFSKext; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; RUN_CLANG_STATIC_ANALYZER = YES; WARNING_CFLAGS = ( "-Werror=missing-prototypes", @@ -832,12 +834,14 @@ 43057C5421E437F300487681 /* Profiling(Release) */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = UBF8T346G9; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; EXECUTABLE_PREFIX = lib; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; }; name = "Profiling(Release)"; @@ -845,9 +849,11 @@ 43057C5521E437F300487681 /* Profiling(Release) */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = UBF8T346G9; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; }; name = "Profiling(Release)"; }; @@ -993,6 +999,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_STATIC_ANALYZER_MODE = deep; + CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = UBF8T346G9; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -1007,6 +1014,7 @@ MODULE_VERSION = 1.0.0d1; PRODUCT_BUNDLE_IDENTIFIER = org.vfsforgit.PrjFSKext; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; RUN_CLANG_STATIC_ANALYZER = YES; WARNING_CFLAGS = ( "-Werror=missing-prototypes", @@ -1020,6 +1028,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_STATIC_ANALYZER_MODE = deep; + CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = UBF8T346G9; GCC_PREPROCESSOR_DEFINITIONS = "MACH_ASSERT=1"; @@ -1030,6 +1039,7 @@ MODULE_VERSION = 1.0.0d1; PRODUCT_BUNDLE_IDENTIFIER = org.vfsforgit.PrjFSKext; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; RUN_CLANG_STATIC_ANALYZER = YES; WARNING_CFLAGS = ( "-Werror=missing-prototypes", @@ -1042,12 +1052,14 @@ 4391F8D721E430CF0008103C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = UBF8T346G9; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; EXECUTABLE_PREFIX = lib; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; }; name = Debug; @@ -1055,12 +1067,14 @@ 4391F8D821E430CF0008103C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = UBF8T346G9; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; EXECUTABLE_PREFIX = lib; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; }; name = Release; @@ -1068,18 +1082,22 @@ 4391F8F921E4355C0008103C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = UBF8T346G9; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Debug; }; 4391F8FA21E4355C0008103C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = UBF8T346G9; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Release; }; @@ -1104,31 +1122,40 @@ 4A08256C21E77B7F00E21AFD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; CREATE_INFOPLIST_SECTION_IN_BINARY = YES; DEVELOPMENT_TEAM = UBF8T346G9; INFOPLIST_FILE = PrjFSKextLogDaemon/Info.plist; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Debug; }; 4A08256D21E77B7F00E21AFD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; CREATE_INFOPLIST_SECTION_IN_BINARY = YES; DEVELOPMENT_TEAM = UBF8T346G9; INFOPLIST_FILE = PrjFSKextLogDaemon/Info.plist; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Release; }; 4A08256E21E77B7F00E21AFD /* Profiling(Release) */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; CREATE_INFOPLIST_SECTION_IN_BINARY = YES; DEVELOPMENT_TEAM = UBF8T346G9; INFOPLIST_FILE = PrjFSKextLogDaemon/Info.plist; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; }; name = "Profiling(Release)"; }; @@ -1202,6 +1229,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_CODE_COVERAGE = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; @@ -1219,6 +1247,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = org.vfsforgit.PrjFSKextTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Debug; }; @@ -1226,6 +1255,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_CODE_COVERAGE = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; @@ -1240,6 +1270,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = org.vfsforgit.PrjFSKextTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Release; }; @@ -1247,6 +1278,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_CODE_COVERAGE = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; @@ -1261,6 +1293,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = org.vfsforgit.PrjFSKextTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; }; name = "Profiling(Release)"; }; diff --git a/ProjFS.Mac/PrjFSKextTests/HandleVnodeOperationTests.mm b/ProjFS.Mac/PrjFSKextTests/HandleVnodeOperationTests.mm index a19c832ece..9daaa11ff4 100644 --- a/ProjFS.Mac/PrjFSKextTests/HandleVnodeOperationTests.mm +++ b/ProjFS.Mac/PrjFSKextTests/HandleVnodeOperationTests.mm @@ -54,38 +54,55 @@ @interface HandleVnodeOperationTests : XCTestCase @end @implementation HandleVnodeOperationTests - -- (void) tearDown { - MockVnodes_CheckAndClear(); + vfs_context_t context; + const char* repoPath; + const char* filePath; + const char* dirPath; + PrjFSProviderUserClient dummyClient; + pid_t dummyClientPid; + shared_ptr testMount; + shared_ptr repoRootVnode; + shared_ptr testFileVnode; + shared_ptr testDirVnode; } -- (void)testHandleVnodeOpEvent { - // Setup +- (void) setUp +{ kern_return_t initResult = VirtualizationRoots_Init(); XCTAssertEqual(initResult, KERN_SUCCESS); - - // Parameters - const char* repoPath = "/Users/test/code/Repo"; - const char* filePath = "/Users/test/code/Repo/file"; - const char* dirPath = "/Users/test/code/Repo/dir"; - vfs_context_t _Nonnull context = vfs_context_create(NULL); - //PerfTracer perfTracer; - PrjFSProviderUserClient dummyClient; - pid_t dummyClientPid=100; + context = vfs_context_create(NULL); + dummyClientPid = 100; // Create Vnode Tree - shared_ptr testMount = mount::Create(); - shared_ptr repoRootVnode = testMount->CreateVnodeTree(repoPath, VDIR); - shared_ptr testFileVnode = testMount->CreateVnodeTree(filePath); - shared_ptr testDirVnode = testMount->CreateVnodeTree(dirPath, VDIR); + repoPath = "/Users/test/code/Repo"; + filePath = "/Users/test/code/Repo/file"; + dirPath = "/Users/test/code/Repo/dir"; + testMount = mount::Create(); + repoRootVnode = testMount->CreateVnodeTree(repoPath, VDIR); + testFileVnode = testMount->CreateVnodeTree(filePath); + testDirVnode = testMount->CreateVnodeTree(dirPath, VDIR); // Register provider for the repository path (Simulate a mount) VirtualizationRootResult result = VirtualizationRoot_RegisterProviderForPath(&dummyClient, dummyClientPid, repoPath); XCTAssertEqual(result.error, 0); vnode_put(s_virtualizationRoots[result.root].rootVNode); - - // Read a file that has not been hydrated yet +} + +- (void) tearDown +{ + testMount.reset(); + repoRootVnode.reset(); + testFileVnode.reset(); + testDirVnode.reset(); + VirtualizationRoots_Cleanup(); + vfs_context_rele(context); + MockVnodes_CheckAndClear(); + MockCalls::Clear(); + callCount = 0; +} + +- (void) testReadDataFileEmpty { expectedMessageType[0] = MessageType_KtoU_HydrateFile; testFileVnode->attrValues.va_flags = FileFlags_IsEmpty | FileFlags_IsInVirtualizationRoot; HandleVnodeOperation( @@ -97,10 +114,9 @@ - (void)testHandleVnodeOpEvent { 0, 0); XCTAssertTrue(MockCalls::DidCallFunction(ProviderMessaging_TrySendRequestAndWaitForResponse)); - MockCalls::Clear(); - callCount = 0; +} - // Ensure a file without an IsEmpty tag is not hydrated +- (void) testReadDataFileHydrated { testFileVnode->attrValues.va_flags = FileFlags_IsInVirtualizationRoot; HandleVnodeOperation( nullptr, @@ -111,9 +127,9 @@ - (void)testHandleVnodeOpEvent { 0, 0); XCTAssertFalse(MockCalls::DidCallFunction(ProviderMessaging_TrySendRequestAndWaitForResponse)); - MockCalls::Clear(); +} - // Ensure a file is Deleted +- (void) testDeleteFile { expectedMessageType[0] = MessageType_KtoU_NotifyFilePreDelete; testFileVnode->attrValues.va_flags = FileFlags_IsInVirtualizationRoot; HandleVnodeOperation( @@ -125,10 +141,9 @@ - (void)testHandleVnodeOpEvent { 0, 0); XCTAssertTrue(MockCalls::DidCallFunction(ProviderMessaging_TrySendRequestAndWaitForResponse)); - MockCalls::Clear(); - callCount = 0; +} - // Ensure a directory is Deleted +- (void) testDeleteDir { expectedMessageType[0] = MessageType_KtoU_NotifyDirectoryPreDelete; expectedMessageType[1] = MessageType_KtoU_RecursivelyEnumerateDirectory; testDirVnode->attrValues.va_flags = FileFlags_IsInVirtualizationRoot; @@ -141,12 +156,27 @@ - (void)testHandleVnodeOpEvent { 0, 0); XCTAssertTrue(MockCalls::DidCallFunction(ProviderMessaging_TrySendRequestAndWaitForResponse)); - MockCalls::Clear(); - callCount = 0; - - // Teardown - VirtualizationRoots_Cleanup(); - vfs_context_rele(context); } +/* +Is Directory + KAUTH_VNODE_LIST_DIRECTORY | + KAUTH_VNODE_SEARCH | + KAUTH_VNODE_READ_SECURITY | + KAUTH_VNODE_READ_ATTRIBUTES | + KAUTH_VNODE_READ_EXTATTRIBUTES | + KAUTH_VNODE_DELETE)) + +Else + + KAUTH_VNODE_READ_ATTRIBUTES | + KAUTH_VNODE_WRITE_ATTRIBUTES | + KAUTH_VNODE_READ_EXTATTRIBUTES | + KAUTH_VNODE_WRITE_EXTATTRIBUTES | + KAUTH_VNODE_READ_DATA | + KAUTH_VNODE_WRITE_DATA | + KAUTH_VNODE_EXECUTE | + KAUTH_VNODE_DELETE)) + // KAUTH_VNODE_WRITE_DATA (Placeholder/Not a placeholder) +*/ @end From 66066e90dd88f4fa756bb206dea648e7278a9054 Mon Sep 17 00:00:00 2001 From: Phil Dennis-Jordan Date: Wed, 20 Mar 2019 22:13:48 +0100 Subject: [PATCH 4/5] Mac kext testing: Implementation of mock function argument checking --- ProjFS.Mac/PrjFS.xcodeproj/project.pbxproj | 3 + .../PrjFSKextTests/KextMockUtilities.cpp | 4 + .../PrjFSKextTests/KextMockUtilities.hpp | 75 +++++++++++++++++-- .../VirtualizationRootsTests.mm | 50 ++++++++++++- 4 files changed, 121 insertions(+), 11 deletions(-) diff --git a/ProjFS.Mac/PrjFS.xcodeproj/project.pbxproj b/ProjFS.Mac/PrjFS.xcodeproj/project.pbxproj index 8da22b36fa..545aac8597 100644 --- a/ProjFS.Mac/PrjFS.xcodeproj/project.pbxproj +++ b/ProjFS.Mac/PrjFS.xcodeproj/project.pbxproj @@ -1228,6 +1228,7 @@ F5E39C7C21F1118D006D65C2 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_ENABLE_CODE_COVERAGE = YES; CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; @@ -1254,6 +1255,7 @@ F5E39C7D21F1118D006D65C2 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_ENABLE_CODE_COVERAGE = YES; CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; @@ -1277,6 +1279,7 @@ F5E39C7E21F1118D006D65C2 /* Profiling(Release) */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_ENABLE_CODE_COVERAGE = YES; CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; diff --git a/ProjFS.Mac/PrjFSKextTests/KextMockUtilities.cpp b/ProjFS.Mac/PrjFSKextTests/KextMockUtilities.cpp index 031ea4e827..2dafcb101e 100644 --- a/ProjFS.Mac/PrjFSKextTests/KextMockUtilities.cpp +++ b/ProjFS.Mac/PrjFSKextTests/KextMockUtilities.cpp @@ -1,6 +1,10 @@ #include "KextMockUtilities.hpp" MockCalls MockCalls::singleton; +namespace KextMock +{ + PlaceholderValue _; +} void MockCalls::Clear() { diff --git a/ProjFS.Mac/PrjFSKextTests/KextMockUtilities.hpp b/ProjFS.Mac/PrjFSKextTests/KextMockUtilities.hpp index ea43976ece..57bdab0804 100644 --- a/ProjFS.Mac/PrjFSKextTests/KextMockUtilities.hpp +++ b/ProjFS.Mac/PrjFSKextTests/KextMockUtilities.hpp @@ -3,6 +3,14 @@ #include #include +namespace KextMock +{ + struct PlaceholderValue + {}; + + extern PlaceholderValue _; +}; + class FunctionCallRecorder { public: @@ -14,10 +22,12 @@ template class SpecificFunctionCallRecorder : public FunctionCallRecorder { typedef R (*FunctionPointerType)(ARGS...); + typedef std::tuple::type...> ArgumentValueTuple; + struct FunctionCall { - // TODO: add argument values uint64_t callSequenceNumber; + std::unique_ptr argumentValues; }; typedef std::unordered_multimap RecordedCallMap; @@ -25,9 +35,11 @@ template RecordedCallMap recordedCalls; static SpecificFunctionCallRecorder functionTypeRegister; - void RecordFunctionCall(FunctionPointerType function, uint64_t sequenceNumber); + void RecordFunctionCall(FunctionPointerType function, uint64_t sequenceNumber, std::unique_ptr&& argumentValues); bool DidCallFunction(FunctionPointerType function); - + template + bool DidCallFunction(FunctionPointerType function, CHECK_ARGS... checkArgs); + virtual void Clear() override { this->recordedCalls.clear(); @@ -40,9 +52,9 @@ template SpecificFunctionCallRecorder SpecificFunctionCallRecorder::functionTypeRegister; template - void SpecificFunctionCallRecorder::RecordFunctionCall(FunctionPointerType function, uint64_t sequenceNumber) + void SpecificFunctionCallRecorder::RecordFunctionCall(FunctionPointerType function, uint64_t sequenceNumber, std::unique_ptr&& argumentValues) { - this->recordedCalls.insert(std::make_pair(function, FunctionCall { sequenceNumber })); + this->recordedCalls.insert(std::make_pair(function, FunctionCall { sequenceNumber, std::move(argumentValues) })); } template @@ -52,6 +64,7 @@ template return foundCall != this->recordedCalls.end(); } + class MockCalls { std::unordered_set functionTypeCallRecorders; @@ -60,12 +73,16 @@ class MockCalls static MockCalls singleton; public: - template - static void RecordFunctionCall(R (*fn)(ARGS...), ARGS... args) + template + static void RecordFunctionCall(R (*fn)(ARGS...), ACTUALARGS... args) { singleton.functionTypeCallRecorders.insert(&SpecificFunctionCallRecorder::functionTypeRegister); - SpecificFunctionCallRecorder::functionTypeRegister.RecordFunctionCall(fn, singleton.nextCallSequenceNumber++); + // Makes a copy of all arguments (even if they were passed by reference; pointers stay pointers, however) + typedef std::tuple::type...> ArgumentValueTuple; + std::unique_ptr argumentValues(std::make_unique(args...)); + + SpecificFunctionCallRecorder::functionTypeRegister.RecordFunctionCall(fn, singleton.nextCallSequenceNumber++, std::move(argumentValues)); } template @@ -91,5 +108,47 @@ class MockCalls { return SpecificFunctionCallRecorder::functionTypeRegister.DidCallFunction(fn); } + + template + static bool DidCallFunction(R (*fn)(ARGS...), CHECK_ARGS... checkArgs) + { + return SpecificFunctionCallRecorder::functionTypeRegister.DidCallFunction(fn, checkArgs...); + } }; +template +bool CheckArgument(ARG_T& argument, CHECK_T check) +{ + return argument == check; +} + +template +bool CheckArgument(ARG_T& argument, KextMock::PlaceholderValue) +{ + return true; +} + +template +bool CheckArguments(TUPLE_T& arguments, CHECK_TUPLE_T check, std::index_sequence) +{ + return (CheckArgument(std::get(arguments), std::get(check)) && ...); +} + + +template +template + bool SpecificFunctionCallRecorder::DidCallFunction(FunctionPointerType function, CHECK_ARGS... checkArgs) +{ + std::pair foundCalls = this->recordedCalls.equal_range(function); + for (typename RecordedCallMap::const_iterator foundCall = foundCalls.first; foundCall != foundCalls.second; ++foundCall) + { + if (foundCall->second.argumentValues) + { + if (CheckArguments(*foundCall->second.argumentValues, std::forward_as_tuple(checkArgs...), std::index_sequence_for{})) + { + return true; + } + } + } + return false; +} diff --git a/ProjFS.Mac/PrjFSKextTests/VirtualizationRootsTests.mm b/ProjFS.Mac/PrjFSKextTests/VirtualizationRootsTests.mm index b8d5cf16a5..c1636001c7 100644 --- a/ProjFS.Mac/PrjFSKextTests/VirtualizationRootsTests.mm +++ b/ProjFS.Mac/PrjFSKextTests/VirtualizationRootsTests.mm @@ -16,6 +16,7 @@ using std::shared_ptr; using std::vector; +using KextMock::_; class PrjFSProviderUserClient { @@ -152,8 +153,8 @@ - (void)testRegisterProviderForPath_ExistingRoot XCTAssertEqual(result.root, rootIndex); XCTAssertEqual(s_virtualizationRoots[result.root].providerUserClient, &self->dummyClient); - XCTAssertTrue(MockCalls::DidCallFunction(vfs_setauthcache_ttl)); - XCTAssertTrue(MockCalls::DidCallFunction(ProviderUserClient_UpdatePathProperty)); + XCTAssertTrue(MockCalls::DidCallFunction(vfs_setauthcache_ttl, _, 0)); + XCTAssertTrue(MockCalls::DidCallFunction(ProviderUserClient_UpdatePathProperty, &self->dummyClient, _)); s_virtualizationRoots[result.root].providerUserClient = nullptr; vnode_put(s_virtualizationRoots[result.root].rootVNode); @@ -218,6 +219,49 @@ - (void)testRegisterProviderForPath_InsertionSucceeded } } +- (void)testRegisterProviderForPath_TwoMountPointsInsertionSucceeded +{ + const char* path1 = "/Users/test/code/Repo"; + shared_ptr vnode1 = vnode::Create(self->testMountPoint, path1, VDIR); + + const char* path2 = "/Volumes/Code/Repo"; + shared_ptr secondMountPoint = mount::Create(); + shared_ptr vnode2 = vnode::Create(secondMountPoint, path2, VDIR); + + VirtualizationRootResult result = VirtualizationRoot_RegisterProviderForPath(&self->dummyClient, self->dummyClientPid, path1); + XCTAssertEqual(result.error, 0); + XCTAssertTrue(VirtualizationRoot_IsValidRootHandle(result.root)); + + PrjFSProviderUserClient dummyClient2; + VirtualizationRootResult result2 = VirtualizationRoot_RegisterProviderForPath(&dummyClient2, 1000, path2); + XCTAssertEqual(result2.error, 0); + XCTAssertTrue(VirtualizationRoot_IsValidRootHandle(result2.root)); + + XCTAssertNotEqual(result.root, result2.root); + + if (VirtualizationRoot_IsValidRootHandle(result.root)) + { + XCTAssertEqual(s_virtualizationRoots[result.root].providerUserClient, &self->dummyClient); + + XCTAssertTrue(MockCalls::DidCallFunction(vfs_setauthcache_ttl, self->testMountPoint.get(), _)); + XCTAssertTrue(MockCalls::DidCallFunction(ProviderUserClient_UpdatePathProperty, &self->dummyClient, _)); + + s_virtualizationRoots[result.root].providerUserClient = nullptr; + vnode_put(s_virtualizationRoots[result.root].rootVNode); + } + + if (VirtualizationRoot_IsValidRootHandle(result2.root)) + { + XCTAssertEqual(s_virtualizationRoots[result2.root].providerUserClient, &dummyClient2); + + XCTAssertTrue(MockCalls::DidCallFunction(vfs_setauthcache_ttl, secondMountPoint.get(), 0)); + XCTAssertTrue(MockCalls::DidCallFunction(ProviderUserClient_UpdatePathProperty, &dummyClient2, _)); + + s_virtualizationRoots[result2.root].providerUserClient = nullptr; + vnode_put(s_virtualizationRoots[result2.root].rootVNode); + } +} + - (void)testRegisterProviderForPath_ArrayFull { const char* path = "/Users/test/code/Repo"; @@ -270,7 +314,7 @@ - (void)testRegisterProviderForPath_ExistingRecycledRoot XCTAssertEqual(s_virtualizationRoots[result.root].rootVNode, newVnode.get()); XCTAssertEqual(s_virtualizationRoots[result.root].rootVNodeVid, newVnode->GetVid()); - XCTAssertTrue(MockCalls::DidCallFunction(vfs_setauthcache_ttl)); + XCTAssertTrue(MockCalls::DidCallFunction(vfs_setauthcache_ttl, self->testMountPoint.get(), 0)); XCTAssertTrue(MockCalls::DidCallFunction(ProviderUserClient_UpdatePathProperty)); s_virtualizationRoots[result.root].providerUserClient = nullptr; From ca6cef41dc64d3e6f42cb9fa4af413ed48e01f32 Mon Sep 17 00:00:00 2001 From: Jessica Schumaker Date: Thu, 21 Mar 2019 11:00:07 -0400 Subject: [PATCH 5/5] Added Args --- .../PrjFSKextTests/HandleVnodeOperationTests.mm | 14 +++++++++++++- ProjFS.Mac/PrjFSKextTests/KextMockUtilities.hpp | 15 --------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/ProjFS.Mac/PrjFSKextTests/HandleVnodeOperationTests.mm b/ProjFS.Mac/PrjFSKextTests/HandleVnodeOperationTests.mm index 9daaa11ff4..ad99221b64 100644 --- a/ProjFS.Mac/PrjFSKextTests/HandleVnodeOperationTests.mm +++ b/ProjFS.Mac/PrjFSKextTests/HandleVnodeOperationTests.mm @@ -14,6 +14,7 @@ #include "MockProc.hpp" using std::shared_ptr; +using KextMock::_; class PrjFSProviderUserClient { @@ -113,7 +114,18 @@ - (void) testReadDataFileEmpty { reinterpret_cast(testFileVnode.get()), 0, 0); - XCTAssertTrue(MockCalls::DidCallFunction(ProviderMessaging_TrySendRequestAndWaitForResponse)); + XCTAssertTrue( + MockCalls::DidCallFunction( + ProviderMessaging_TrySendRequestAndWaitForResponse, + _, + MessageType_KtoU_HydrateFile, + _, + _, + _, + _, + _, + _, + _)); } - (void) testReadDataFileHydrated { diff --git a/ProjFS.Mac/PrjFSKextTests/KextMockUtilities.hpp b/ProjFS.Mac/PrjFSKextTests/KextMockUtilities.hpp index 57bdab0804..cad9d0e791 100644 --- a/ProjFS.Mac/PrjFSKextTests/KextMockUtilities.hpp +++ b/ProjFS.Mac/PrjFSKextTests/KextMockUtilities.hpp @@ -85,21 +85,6 @@ class MockCalls SpecificFunctionCallRecorder::functionTypeRegister.RecordFunctionCall(fn, singleton.nextCallSequenceNumber++, std::move(argumentValues)); } - template - static void RecordFunctionCall(R (*fn)(ARGS...)) - { - singleton.functionTypeCallRecorders.insert(&SpecificFunctionCallRecorder::functionTypeRegister); - - SpecificFunctionCallRecorder::functionTypeRegister.RecordFunctionCall(fn, singleton.nextCallSequenceNumber++); - } - - template - static void RecordFunctionCall(R (*fn)(ARGS...), ACTUAL_ARGS... args) - { - singleton.functionTypeCallRecorders.insert(&SpecificFunctionCallRecorder::functionTypeRegister); - - SpecificFunctionCallRecorder::functionTypeRegister.RecordFunctionCall(fn, singleton.nextCallSequenceNumber++); - } static void Clear();