diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext.xcodeproj/project.pbxproj b/ProjFS.Mac/PrjFSKext/PrjFSKext.xcodeproj/project.pbxproj index 74700506e7..dbd7e2453f 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext.xcodeproj/project.pbxproj +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext.xcodeproj/project.pbxproj @@ -408,6 +408,7 @@ "$(inherited)", "MACH_ASSERT=1", ); + GCC_WARN_SHADOW = YES; INFOPLIST_FILE = PrjFSKext/Info.plist; MODULE_NAME = io.gvfs.PrjFSKext; MODULE_START = PrjFSKext_Start; @@ -430,7 +431,11 @@ buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = UBF8T346G9; - GCC_PREPROCESSOR_DEFINITIONS = "MACH_ASSERT=1"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "MACH_ASSERT=1", + "$(inherited)", + ); + GCC_WARN_SHADOW = YES; INFOPLIST_FILE = PrjFSKext/Info.plist; MODULE_NAME = io.gvfs.PrjFSKext; MODULE_START = PrjFSKext_Start; diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp index a0ed82e25c..111c9355d8 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp @@ -42,11 +42,9 @@ static inline bool ActionBitIsSet(kauth_action_t action, kauth_action_t mask); static bool IsFileSystemCrawler(char* procname); -static const char* GetRelativePath(const char* path, const char* root); - static void Sleep(int seconds, void* channel); static bool TrySendRequestAndWaitForResponse( - const VirtualizationRoot* root, + VirtualizationRootHandle root, MessageType messageType, const vnode_t vnode, const FsidInode& vnodeFsidInode, @@ -65,7 +63,7 @@ static bool ShouldHandleVnodeOpEvent( kauth_action_t action, // Out params: - VirtualizationRoot** root, + VirtualizationRootHandle* root, vtype* vnodeType, uint32_t* vnodeFileFlags, FsidInode* vnodeFsidInode, @@ -80,7 +78,7 @@ static bool ShouldHandleFileOpEvent( kauth_action_t action, // Out params: - VirtualizationRoot** root, + VirtualizationRootHandle* root, FsidInode* vnodeFsidInode, int* pid); @@ -260,12 +258,24 @@ static int HandleVnodeOperation( // arg2 is the (vnode_t) parent vnode int* kauthError = reinterpret_cast(arg3); int kauthResult = KAUTH_RESULT_DEFER; + bool putVnodeWhenDone = false; + + // A lot of our file checks such as attribute tests behave oddly if the vnode + // refers to a named fork/stream; apply the logic as if the vnode operation was + // occurring on the file itself. (/path/to/file/..namedfork/rsrc) + if (vnode_isnamedstream(currentVnode)) + { + vnode_t mainFileFork = vnode_getparent(currentVnode); + assert(NULLVP != mainFileFork); + currentVnode = mainFileFork; + putVnodeWhenDone = true; + } const char* vnodePath = nullptr; char vnodePathBuffer[PrjFSMaxPath]; int vnodePathLength = PrjFSMaxPath; - VirtualizationRoot* root = nullptr; + VirtualizationRootHandle root = RootHandle_None; vtype vnodeType; uint32_t currentVnodeFileFlags; FsidInode vnodeFsidInode; @@ -384,6 +394,11 @@ static int HandleVnodeOperation( } CleanupAndReturn: + if (putVnodeWhenDone) + { + vnode_put(currentVnode); + } + atomic_fetch_sub(&s_numActiveKauthEvents, 1); return kauthResult; } @@ -408,7 +423,7 @@ static int HandleFileOpOperation( KAUTH_FILEOP_LINK == action) { // arg0 is the (const char *) fromPath (or the file being linked to) - const char* newPath = (const char*)arg1; + const char* newPath = reinterpret_cast(arg1); // 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 @@ -418,7 +433,7 @@ static int HandleFileOpOperation( goto CleanupAndReturn; } - VirtualizationRoot* root = nullptr; + VirtualizationRootHandle root = RootHandle_None; FsidInode vnodeFsidInode; int pid; if (!ShouldHandleFileOpEvent( @@ -464,7 +479,7 @@ static int HandleFileOpOperation( else if (KAUTH_FILEOP_CLOSE == action) { vnode_t currentVnode = reinterpret_cast(arg0); - const char* path = (const char*)arg1; + const char* path = reinterpret_cast(arg1); int closeFlags = static_cast(arg2); if (vnode_isdir(currentVnode)) @@ -478,7 +493,7 @@ static int HandleFileOpOperation( goto CleanupAndReturn; } - VirtualizationRoot* root = nullptr; + VirtualizationRootHandle root = RootHandle_None; FsidInode vnodeFsidInode; int pid; if (!ShouldHandleFileOpEvent( @@ -554,7 +569,7 @@ static bool ShouldHandleVnodeOpEvent( kauth_action_t action, // Out params: - VirtualizationRoot** root, + VirtualizationRootHandle* root, vtype* vnodeType, uint32_t* vnodeFileFlags, FsidInode* vnodeFsidInode, @@ -562,8 +577,8 @@ static bool ShouldHandleVnodeOpEvent( char procname[MAXCOMLEN + 1], int* kauthResult) { - *root = nullptr; *kauthResult = KAUTH_RESULT_DEFER; + *root = RootHandle_None; if (!VirtualizationRoot_VnodeIsOnAllowedFilesystem(vnode)) { @@ -609,19 +624,22 @@ static bool ShouldHandleVnodeOpEvent( } *vnodeFsidInode = Vnode_GetFsidAndInode(vnode, context); - *root = VirtualizationRoots_FindForVnode(vnode, *vnodeFsidInode); + *root = VirtualizationRoot_FindForVnode(vnode, *vnodeFsidInode); - if (nullptr == *root) + if (RootHandle_ProviderTemporaryDirectory == *root) + { + *kauthResult = KAUTH_RESULT_DEFER; + return false; + } + else if (RootHandle_None == *root) { KextLog_FileNote(vnode, "No virtualization root found for file with set flag."); *kauthResult = KAUTH_RESULT_DEFER; return false; } - else if (nullptr == (*root)->providerUserClient) + else if (!VirtualizationRoot_IsOnline(*root)) { - // There is no registered provider for this root - // TODO(Mac): Protect files in the worktree from modification (and prevent // the creation of new files) when the provider is offline @@ -630,7 +648,7 @@ static bool ShouldHandleVnodeOpEvent( } // If the calling process is the provider, we must exit right away to avoid deadlocks - if (*pid == (*root)->providerPid) + if (VirtualizationRoot_PIDMatchesProvider(*root, *pid)) { *kauthResult = KAUTH_RESULT_DEFER; return false; @@ -646,10 +664,12 @@ static bool ShouldHandleFileOpEvent( kauth_action_t action, // Out params: - VirtualizationRoot** root, + VirtualizationRootHandle* root, FsidInode* vnodeFsidInode, int* pid) { + *root = RootHandle_None; + vtype vnodeType = vnode_vtype(vnode); if (ShouldIgnoreVnodeType(vnodeType, vnode)) { @@ -657,21 +677,17 @@ static bool ShouldHandleFileOpEvent( } *vnodeFsidInode = Vnode_GetFsidAndInode(vnode, context); - *root = VirtualizationRoots_FindForVnode(vnode, *vnodeFsidInode); - if (nullptr == *root) - { - return false; - } - else if (nullptr == (*root)->providerUserClient) + *root = VirtualizationRoot_FindForVnode(vnode, *vnodeFsidInode); + if (!VirtualizationRoot_IsValidRootHandle(*root)) { - // There is no registered provider for this root + // This VNode is not part of a root return false; } - // If the calling process is the provider, we must exit right away to avoid deadlocks *pid = GetPid(context); - if (*pid == (*root)->providerPid) + if (VirtualizationRoot_PIDMatchesProvider(*root, *pid)) { + // If the calling process is the provider, we must exit right away to avoid deadlocks return false; } @@ -679,7 +695,7 @@ static bool ShouldHandleFileOpEvent( } static bool TrySendRequestAndWaitForResponse( - const VirtualizationRoot* root, + VirtualizationRootHandle root, MessageType messageType, const vnode_t vnode, const FsidInode& vnodeFsidInode, @@ -702,7 +718,7 @@ static bool TrySendRequestAndWaitForResponse( return false; } - const char* relativePath = GetRelativePath(vnodePath, root->path); + const char* relativePath = VirtualizationRoot_GetRootRelativePath(root, vnodePath); int nextMessageId = OSIncrementAtomic(&s_nextMessageId); @@ -731,7 +747,7 @@ static bool TrySendRequestAndWaitForResponse( // TODO(Mac): Should we pass in the root directly, rather than root->index? // The index seems more like a private implementation detail. - if (!isShuttingDown && 0 != ActiveProvider_SendMessage(root->index, messageSpec)) + if (!isShuttingDown && 0 != ActiveProvider_SendMessage(root, messageSpec)) { // TODO: appropriately handle unresponsive providers @@ -868,19 +884,6 @@ static bool IsFileSystemCrawler(char* procname) return false; } -static const char* GetRelativePath(const char* path, const char* root) -{ - assert(strlen(path) >= strlen(root)); - - const char* relativePath = path + strlen(root); - if (relativePath[0] == '/') - { - relativePath++; - } - - return relativePath; -} - static bool ShouldIgnoreVnodeType(vtype vnodeType, vnode_t vnode) { switch (vnodeType) diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/Memory.hpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/Memory.hpp index eddfdf0fc5..0749a65533 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/Memory.hpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/Memory.hpp @@ -7,4 +7,17 @@ kern_return_t Memory_Cleanup(); void* Memory_Alloc(uint32_t size); void Memory_Free(void* buffer, uint32_t size); +template +T* Memory_AllocArray(uint32_t arrayLength) +{ + size_t allocBytes = arrayLength * sizeof(T); + if (allocBytes > UINT32_MAX) + { + return nullptr; + } + + return static_cast(Memory_Alloc(static_cast(allocBytes))); +} + + #endif /* Memory_h */ diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/PrjFSProviderUserClient.cpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/PrjFSProviderUserClient.cpp index 6e9b75427c..83b2fcb19b 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/PrjFSProviderUserClient.cpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/PrjFSProviderUserClient.cpp @@ -19,7 +19,11 @@ static const IOExternalMethodDispatch ProviderUserClientDispatch[] = { [ProviderSelector_RegisterVirtualizationRootPath] = { - &PrjFSProviderUserClient::registerVirtualizationRoot, 0, kIOUCVariableStructureSize, 1, 0 + .function = &PrjFSProviderUserClient::registerVirtualizationRoot, + .checkScalarInputCount = 0, + .checkStructureInputSize = kIOUCVariableStructureSize, // null-terminated string: virtualisation root path + .checkScalarOutputCount = 1, // returned errno + .checkStructureOutputSize = 0 }, [ProviderSelector_KernelMessageResponse] = { @@ -37,7 +41,7 @@ bool PrjFSProviderUserClient::initWithTask( UInt32 type, OSDictionary* properties) { - this->virtualizationRootIndex = -1; + this->virtualizationRootHandle = RootHandle_None; this->pid = proc_selfpid(); if (!this->super::initWithTask(owningTask, securityToken, type, properties)) @@ -92,9 +96,9 @@ void PrjFSProviderUserClient::free() // the connection. IOReturn PrjFSProviderUserClient::clientClose() { - int32_t root = this->virtualizationRootIndex; - this->virtualizationRootIndex = -1; - if (-1 != root) + VirtualizationRootHandle root = this->virtualizationRootHandle; + this->virtualizationRootHandle = RootHandle_None; + if (RootHandle_None != root) { ActiveProvider_Disconnect(root); } @@ -210,7 +214,7 @@ IOReturn PrjFSProviderUserClient::registerVirtualizationRoot(const char* rootPat *outError = EINVAL; return kIOReturnSuccess; } - else if (this->virtualizationRootIndex != -1) + else if (this->virtualizationRootHandle != RootHandle_None) { // Already set *outError = EBUSY; @@ -220,11 +224,11 @@ IOReturn PrjFSProviderUserClient::registerVirtualizationRoot(const char* rootPat VirtualizationRootResult result = VirtualizationRoot_RegisterProviderForPath(this, this->pid, rootPath); if (0 == result.error) { - this->virtualizationRootIndex = result.rootIndex; + this->virtualizationRootHandle = result.root; // Sets the root index in the IORegistry for diagnostic purposes char location[5] = ""; - snprintf(location, sizeof(location), "%d", result.rootIndex); + snprintf(location, sizeof(location), "%d", result.root); this->setLocation(location); } diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/PrjFSProviderUserClient.hpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/PrjFSProviderUserClient.hpp index 12621ede4d..de487108c1 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/PrjFSProviderUserClient.hpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/PrjFSProviderUserClient.hpp @@ -3,6 +3,7 @@ #include "PrjFSClasses.hpp" #include "Locks.hpp" #include "Message.h" +#include "VirtualizationRoots.hpp" #include struct MessageHeader; @@ -18,8 +19,8 @@ class PrjFSProviderUserClient : public IOUserClient Mutex dataQueueWriterMutex; public: pid_t pid; - // The root for which this is the provider; -1 prior to registration - int32_t virtualizationRootIndex; + // The root for which this is the provider; RootHandle_None prior to registration + VirtualizationRootHandle virtualizationRootHandle; // IOUserClient methods: virtual bool initWithTask( diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.cpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.cpp index 46a5704d9f..88b0a97dba 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.cpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "PrjFSCommon.h" #include "PrjFSXattrs.h" @@ -12,17 +13,82 @@ #include "VnodeUtilities.hpp" +struct VirtualizationRoot +{ + bool inUse; + // If this is a nullptr, there is no active provider for this virtualization root (offline root) + PrjFSProviderUserClient* providerUserClient; + int providerPid; + // For an active root, this is retained (vnode_get), for an offline one, it is not, so it may be stale (check the vid) + vnode_t rootVNode; + uint32_t rootVNodeVid; + + // Mount point ID + persistent, on-disk ID for the root directory, so we can + // identify it if the vnode of an offline root gets recycled. + fsid_t rootFsid; + uint64_t rootInode; + + // TODO(Mac): this should eventually be entirely diagnostic and not used for decisions + char path[PrjFSMaxPath]; +}; + static RWLock s_rwLock = {}; -// Arbitrary choice, but prevents user space attacker from causing -// allocation of too much wired kernel memory. -static const size_t MaxVirtualizationRoots = 128; +// Current length of the s_virtualizationRoots array +static uint16_t s_maxVirtualizationRoots = 0; +static VirtualizationRoot* s_virtualizationRoots = nullptr; + +// Looks up the vnode/vid and fsid/inode pairs among the known roots +static VirtualizationRootHandle FindRootAtVnode_Locked(vnode_t vnode, uint32_t vid, FsidInode fileId); -static VirtualizationRoot s_virtualizationRoots[MaxVirtualizationRoots] = {}; +// Looks up the vnode and fsid/inode pair among the known roots, and if not found, +// detects if there is a hitherto-unknown root at vnode by checking attributes. +static VirtualizationRootHandle FindOrDetectRootAtVnode(vnode_t vnode, vfs_context_t context, const FsidInode& vnodeFsidInode); -static int16_t FindRootForVnode_Locked(vnode_t vnode, uint32_t vid, FsidInode fileId); -static int16_t FindUnusedIndex_Locked(); -static int16_t InsertVirtualizationRoot_Locked(PrjFSProviderUserClient* userClient, pid_t clientPID, vnode_t vnode, uint32_t vid, FsidInode persistentIds, const char* path); +static VirtualizationRootHandle FindUnusedIndex_Locked(); +static VirtualizationRootHandle InsertVirtualizationRoot_Locked(PrjFSProviderUserClient* userClient, pid_t clientPID, vnode_t vnode, uint32_t vid, FsidInode persistentIds, const char* path); + +bool VirtualizationRoot_IsOnline(VirtualizationRootHandle rootHandle) +{ + if (rootHandle < 0) + { + return false; + } + + bool result; + RWLock_AcquireShared(s_rwLock); + { + result = + rootHandle < s_maxVirtualizationRoots + && s_virtualizationRoots[rootHandle].inUse + && nullptr != s_virtualizationRoots[rootHandle].providerUserClient; + } + RWLock_ReleaseShared(s_rwLock); + + return result; +} + +bool VirtualizationRoot_PIDMatchesProvider(VirtualizationRootHandle rootHandle, pid_t pid) +{ + bool result; + RWLock_AcquireShared(s_rwLock); + { + result = + rootHandle >= 0 + && rootHandle < s_maxVirtualizationRoots + && s_virtualizationRoots[rootHandle].inUse + && nullptr != s_virtualizationRoots[rootHandle].providerUserClient + && pid == s_virtualizationRoots[rootHandle].providerPid; + } + RWLock_ReleaseShared(s_rwLock); + + return result; +} + +bool VirtualizationRoot_IsValidRootHandle(VirtualizationRootHandle rootIndex) +{ + return (rootIndex > RootHandle_None); +} kern_return_t VirtualizationRoots_Init() { @@ -37,11 +103,20 @@ kern_return_t VirtualizationRoots_Init() return KERN_FAILURE; } - for (uint32_t i = 0; i < MaxVirtualizationRoots; ++i) + s_maxVirtualizationRoots = 128; + s_virtualizationRoots = Memory_AllocArray(s_maxVirtualizationRoots); + if (nullptr == s_virtualizationRoots) + { + return KERN_RESOURCE_SHORTAGE; + } + + for (VirtualizationRootHandle i = 0; i < s_maxVirtualizationRoots; ++i) { - s_virtualizationRoots[i].index = i; + s_virtualizationRoots[i] = VirtualizationRoot{ }; } + atomic_thread_fence(memory_order_seq_cst); + return KERN_SUCCESS; } @@ -56,18 +131,19 @@ kern_return_t VirtualizationRoots_Cleanup() return KERN_FAILURE; } -VirtualizationRoot* VirtualizationRoots_FindForVnode(vnode_t vnode, const FsidInode& vnodeFsidInode) +VirtualizationRootHandle VirtualizationRoot_FindForVnode(vnode_t vnode, const FsidInode& vnodeFsidInode) { - VirtualizationRoot* root = nullptr; + VirtualizationRootHandle rootHandle = RootHandle_None; vnode_get(vnode); // Search up the tree until we hit a known virtualization root or THE root of the file system - while (nullptr == root && NULLVP != vnode && !vnode_isvroot(vnode)) + while (RootHandle_None == rootHandle && NULLVP != vnode && !vnode_isvroot(vnode)) { - int16_t rootIndex = VirtualizationRoots_LookupVnode(vnode, /*context*/ nullptr, vnodeFsidInode); - if (rootIndex >= 0) + rootHandle = FindOrDetectRootAtVnode(vnode, nullptr /* vfs context */, vnodeFsidInode); + // Note: if FindOrDetectRootAtVnode returns a "special" handle other + // than RootHandle_None, we want to stop the search and return that. + if (rootHandle != RootHandle_None) { - root = &s_virtualizationRoots[rootIndex]; break; } @@ -81,22 +157,22 @@ VirtualizationRoot* VirtualizationRoots_FindForVnode(vnode_t vnode, const FsidIn vnode_put(vnode); } - return root; + return rootHandle; } -int16_t VirtualizationRoots_LookupVnode(vnode_t vnode, vfs_context_t context, const FsidInode& vnodeFsidInode) +static VirtualizationRootHandle FindOrDetectRootAtVnode(vnode_t vnode, vfs_context_t context, const FsidInode& vnodeFsidInode) { uint32_t vid = vnode_vid(vnode); - int16_t rootIndex; + VirtualizationRootHandle rootIndex; RWLock_AcquireShared(s_rwLock); { - rootIndex = FindRootForVnode_Locked(vnode, vid, vnodeFsidInode); + rootIndex = FindRootAtVnode_Locked(vnode, vid, vnodeFsidInode); } RWLock_ReleaseShared(s_rwLock); - if (rootIndex < 0) + if (rootIndex == RootHandle_None) { PrjFSVirtualizationRootXAttrData rootXattr = {}; SizeOrError xattrResult = Vnode_ReadXattr(vnode, PrjFSVirtualizationRootXAttrName, &rootXattr, sizeof(rootXattr), context); @@ -111,9 +187,9 @@ int16_t VirtualizationRoots_LookupVnode(vnode_t vnode, vfs_context_t context, co RWLock_AcquireExclusive(s_rwLock); { // Vnode may already have been inserted as a root in the interim - rootIndex = FindRootForVnode_Locked(vnode, vid, vnodeFsidInode); + rootIndex = FindRootAtVnode_Locked(vnode, vid, vnodeFsidInode); - if (rootIndex < 0) + if (RootHandle_None == rootIndex) { // Insert new offline root rootIndex = InsertVirtualizationRoot_Locked(nullptr, 0, vnode, vid, vnodeFsidInode, path); @@ -130,9 +206,9 @@ int16_t VirtualizationRoots_LookupVnode(vnode_t vnode, vfs_context_t context, co return rootIndex; } -static int16_t FindUnusedIndex_Locked() +static VirtualizationRootHandle FindUnusedIndex_Locked() { - for (int16_t i = 0; i < MaxVirtualizationRoots; ++i) + for (VirtualizationRootHandle i = 0; i < s_maxVirtualizationRoots; ++i) { if (!s_virtualizationRoots[i].inUse) { @@ -140,7 +216,43 @@ static int16_t FindUnusedIndex_Locked() } } - return -1; + return RootHandle_None; +} + +static VirtualizationRootHandle FindUnusedIndexOrGrow_Locked() +{ + VirtualizationRootHandle rootIndex = FindUnusedIndex_Locked(); + + if (RootHandle_None == rootIndex) + { + // No space, resize array + uint16_t newLength = MIN(s_maxVirtualizationRoots * 2u, INT16_MAX + 1u); + if (newLength <= s_maxVirtualizationRoots) + { + return RootHandle_None; + } + + VirtualizationRoot* grownArray = Memory_AllocArray(newLength); + if (nullptr == grownArray) + { + return RootHandle_None; + } + + uint32_t oldSizeBytes = sizeof(s_virtualizationRoots[0]) * s_maxVirtualizationRoots; + memcpy(grownArray, s_virtualizationRoots, oldSizeBytes); + Memory_Free(s_virtualizationRoots, oldSizeBytes); + s_virtualizationRoots = grownArray; + + for (uint16_t i = s_maxVirtualizationRoots; i < newLength; ++i) + { + s_virtualizationRoots[i] = VirtualizationRoot{ }; + } + + rootIndex = s_maxVirtualizationRoots; + s_maxVirtualizationRoots = newLength; + } + + return rootIndex; } static bool FsidsAreEqual(fsid_t a, fsid_t b) @@ -148,9 +260,9 @@ static bool FsidsAreEqual(fsid_t a, fsid_t b) return a.val[0] == b.val[0] && a.val[1] == b.val[1]; } -static int16_t FindRootForVnode_Locked(vnode_t vnode, uint32_t vid, FsidInode fileId) +static VirtualizationRootHandle FindRootAtVnode_Locked(vnode_t vnode, uint32_t vid, FsidInode fileId) { - for (int16_t i = 0; i < MaxVirtualizationRoots; ++i) + for (VirtualizationRootHandle i = 0; i < s_maxVirtualizationRoots; ++i) { VirtualizationRoot& rootEntry = s_virtualizationRoots[i]; if (!rootEntry.inUse) @@ -172,24 +284,22 @@ static int16_t FindRootForVnode_Locked(vnode_t vnode, uint32_t vid, FsidInode fi return i; } } - return -1; + return RootHandle_None; } // Returns negative value if it failed, or inserted index on success -static int16_t InsertVirtualizationRoot_Locked(PrjFSProviderUserClient* userClient, pid_t clientPID, vnode_t vnode, uint32_t vid, FsidInode persistentIds, const char* path) +static VirtualizationRootHandle InsertVirtualizationRoot_Locked(PrjFSProviderUserClient* userClient, pid_t clientPID, vnode_t vnode, uint32_t vid, FsidInode persistentIds, const char* path) { - // New root - int16_t rootIndex = FindUnusedIndex_Locked(); + VirtualizationRootHandle rootIndex = FindUnusedIndexOrGrow_Locked(); - if (rootIndex >= 0) + if (RootHandle_None != rootIndex) { - assert(rootIndex < MaxVirtualizationRoots); + assert(rootIndex < s_maxVirtualizationRoots); VirtualizationRoot* root = &s_virtualizationRoots[rootIndex]; root->providerUserClient = userClient; root->providerPid = clientPID; root->inUse = true; - root->index = rootIndex; root->rootVNode = vnode; root->rootVNodeVid = vid; @@ -215,7 +325,7 @@ VirtualizationRootResult VirtualizationRoot_RegisterProviderForPath(PrjFSProvide vnode_t virtualizationRootVNode = NULLVP; vfs_context_t vfsContext = vfs_context_create(nullptr); - int32_t rootIndex = -1; + VirtualizationRootHandle rootIndex = RootHandle_None; errno_t err = vnode_lookup(virtualizationRootPath, 0 /* flags */, &virtualizationRootVNode, vfsContext); if (0 == err) { @@ -234,7 +344,7 @@ VirtualizationRootResult VirtualizationRoot_RegisterProviderForPath(PrjFSProvide RWLock_AcquireExclusive(s_rwLock); { - rootIndex = FindRootForVnode_Locked(virtualizationRootVNode, rootVid, vnodeIds); + rootIndex = FindRootAtVnode_Locked(virtualizationRootVNode, rootVid, vnodeIds); if (rootIndex >= 0) { // Reattaching to existing root @@ -242,7 +352,7 @@ VirtualizationRootResult VirtualizationRoot_RegisterProviderForPath(PrjFSProvide { // Only one provider per root err = EBUSY; - rootIndex = -1; + rootIndex = RootHandle_None; } else { @@ -257,7 +367,7 @@ VirtualizationRootResult VirtualizationRoot_RegisterProviderForPath(PrjFSProvide rootIndex = InsertVirtualizationRoot_Locked(userClient, clientPID, virtualizationRootVNode, rootVid, vnodeIds, virtualizationRootPath); if (rootIndex >= 0) { - assert(rootIndex < MaxVirtualizationRoots); + assert(rootIndex < s_maxVirtualizationRoots); VirtualizationRoot* root = &s_virtualizationRoots[rootIndex]; strlcpy(root->path, virtualizationRootPath, sizeof(root->path)); @@ -292,13 +402,13 @@ VirtualizationRootResult VirtualizationRoot_RegisterProviderForPath(PrjFSProvide return VirtualizationRootResult { err, rootIndex }; } -void ActiveProvider_Disconnect(int32_t rootIndex) +void ActiveProvider_Disconnect(VirtualizationRootHandle rootIndex) { assert(rootIndex >= 0); - assert(rootIndex <= MaxVirtualizationRoots); - RWLock_AcquireExclusive(s_rwLock); { + assert(rootIndex <= s_maxVirtualizationRoots); + VirtualizationRoot* root = &s_virtualizationRoots[rootIndex]; assert(nullptr != root->providerUserClient); @@ -311,22 +421,23 @@ void ActiveProvider_Disconnect(int32_t rootIndex) RWLock_ReleaseExclusive(s_rwLock); } -errno_t ActiveProvider_SendMessage(int32_t rootIndex, const Message message) +errno_t ActiveProvider_SendMessage(VirtualizationRootHandle rootIndex, const Message message) { assert(rootIndex >= 0); - assert(rootIndex < MaxVirtualizationRoots); PrjFSProviderUserClient* userClient = nullptr; - RWLock_AcquireExclusive(s_rwLock); + RWLock_AcquireShared(s_rwLock); { + assert(rootIndex < s_maxVirtualizationRoots); + userClient = s_virtualizationRoots[rootIndex].providerUserClient; if (nullptr != userClient) { userClient->retain(); } } - RWLock_ReleaseExclusive(s_rwLock); + RWLock_ReleaseShared(s_rwLock); if (nullptr != userClient) { @@ -356,3 +467,32 @@ bool VirtualizationRoot_VnodeIsOnAllowedFilesystem(vnode_t vnode) || 0 == strncmp("apfs", vfsStat->f_fstypename, sizeof(vfsStat->f_fstypename)); } +static const char* GetRelativePath(const char* path, const char* root) +{ + assert(strlen(path) >= strlen(root)); + + const char* relativePath = path + strlen(root); + if (relativePath[0] == '/') + { + relativePath++; + } + + return relativePath; +} + +const char* VirtualizationRoot_GetRootRelativePath(VirtualizationRootHandle rootIndex, const char* path) +{ + assert(rootIndex >= 0); + + const char* relativePath; + + RWLock_AcquireShared(s_rwLock); + { + assert(rootIndex < s_maxVirtualizationRoots); + assert(s_virtualizationRoots[rootIndex].inUse); + relativePath = GetRelativePath(path, s_virtualizationRoots[rootIndex].path); + } + RWLock_ReleaseShared(s_rwLock); + + return relativePath; +} diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.hpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.hpp index 3d1c86aefa..2b19b0681d 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.hpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.hpp @@ -4,42 +4,37 @@ #include "PrjFSClasses.hpp" #include "kernel-header-wrappers/vnode.h" -struct VirtualizationRoot -{ - bool inUse; - // If this is a nullptr, there is no active provider for this virtualization root (offline root) - PrjFSProviderUserClient* providerUserClient; - int providerPid; - // For an active root, this is retained (vnode_get), for an offline one, it is not, so it may be stale (check the vid) - vnode_t rootVNode; - uint32_t rootVNodeVid; - - // Mount point ID + persistent, on-disk ID for the root directory, so we can - // identify it if the vnode of an offline root gets recycled. - fsid_t rootFsid; - uint64_t rootInode; - - // TODO(Mac): this should eventually be entirely diagnostic and not used for decisions - char path[PrjFSMaxPath]; +typedef int16_t VirtualizationRootHandle; - int32_t index; +// Zero and positive values indicate a handle for a valid virtualization +// root. Other values have special meanings: +enum VirtualizationRootSpecialHandle : VirtualizationRootHandle +{ + // Not in a virtualization root. + RootHandle_None = -1, + // Root/non-root state not known. Useful reset value for invalidating cached state. + RootHandle_Indeterminate = -2, + // Vnode is not in a virtualization root, but below a provider's registered temp directory + RootHandle_ProviderTemporaryDirectory = -3, }; kern_return_t VirtualizationRoots_Init(void); kern_return_t VirtualizationRoots_Cleanup(void); -VirtualizationRoot* VirtualizationRoots_FindForVnode(vnode_t vnode, const FsidInode& vnodeFsidInode); +VirtualizationRootHandle VirtualizationRoot_FindForVnode(vnode_t vnode, const FsidInode& vnodeFsidInode); struct VirtualizationRootResult { errno_t error; - int32_t rootIndex; + VirtualizationRootHandle root; }; VirtualizationRootResult VirtualizationRoot_RegisterProviderForPath(PrjFSProviderUserClient* userClient, pid_t clientPID, const char* virtualizationRootPath); -void ActiveProvider_Disconnect(int32_t rootIndex); +void ActiveProvider_Disconnect(VirtualizationRootHandle rootHandle); struct Message; -errno_t ActiveProvider_SendMessage(int32_t rootIndex, const Message message); +errno_t ActiveProvider_SendMessage(VirtualizationRootHandle rootHandle, const Message message); bool VirtualizationRoot_VnodeIsOnAllowedFilesystem(vnode_t vnode); - -int16_t VirtualizationRoots_LookupVnode(vnode_t vnode, vfs_context_t context, const FsidInode& vnodeFsidInode); +bool VirtualizationRoot_IsOnline(VirtualizationRootHandle rootHandle); +bool VirtualizationRoot_PIDMatchesProvider(VirtualizationRootHandle rootHandle, pid_t pid); +bool VirtualizationRoot_IsValidRootHandle(VirtualizationRootHandle rootHandle); +const char* VirtualizationRoot_GetRootRelativePath(VirtualizationRootHandle rootHandle, const char* path);