summaryrefslogtreecommitdiff
path: root/src/pal/src/sharedmemory/sharedmemory.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/pal/src/sharedmemory/sharedmemory.cpp')
-rw-r--r--src/pal/src/sharedmemory/sharedmemory.cpp1136
1 files changed, 1136 insertions, 0 deletions
diff --git a/src/pal/src/sharedmemory/sharedmemory.cpp b/src/pal/src/sharedmemory/sharedmemory.cpp
new file mode 100644
index 0000000000..7f25cae49e
--- /dev/null
+++ b/src/pal/src/sharedmemory/sharedmemory.cpp
@@ -0,0 +1,1136 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include "pal/sharedmemory.h"
+
+#include "pal/dbgmsg.h"
+#include "pal/file.hpp"
+#include "pal/malloc.hpp"
+#include "pal/thread.hpp"
+#include "pal/virtual.h"
+
+#include <sys/file.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+using namespace CorUnix;
+
+SET_DEFAULT_DEBUG_CHANNEL(SHMEM);
+
+#include "pal/sharedmemory.inl"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// AutoFreeBuffer
+
+AutoFreeBuffer::AutoFreeBuffer(void *buffer) : m_buffer(buffer), m_cancel(false)
+{
+}
+
+AutoFreeBuffer::~AutoFreeBuffer()
+{
+ if (!m_cancel && m_buffer != nullptr)
+ {
+ free(m_buffer);
+ }
+}
+
+void AutoFreeBuffer::Cancel()
+{
+ m_cancel = true;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// SharedMemoryException
+
+SharedMemoryException::SharedMemoryException(DWORD errorCode) : m_errorCode(errorCode)
+{
+}
+
+DWORD SharedMemoryException::GetErrorCode() const
+{
+ return m_errorCode;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// SharedMemoryHelpers
+
+const mode_t SharedMemoryHelpers::PermissionsMask_AllUsers_ReadWrite =
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
+const mode_t SharedMemoryHelpers::PermissionsMask_AllUsers_ReadWriteExecute =
+ PermissionsMask_AllUsers_ReadWrite | (S_IXUSR | S_IXGRP | S_IXOTH);
+const UINT32 SharedMemoryHelpers::InvalidProcessId = static_cast<UINT32>(-1);
+const SIZE_T SharedMemoryHelpers::InvalidThreadId = static_cast<SIZE_T>(-1);
+const UINT64 SharedMemoryHelpers::InvalidSharedThreadId = static_cast<UINT64>(-1);
+
+void *SharedMemoryHelpers::Alloc(SIZE_T byteCount)
+{
+ void *buffer = InternalMalloc(byteCount);
+ if (buffer == nullptr)
+ {
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::OutOfMemory));
+ }
+ return buffer;
+}
+
+SIZE_T SharedMemoryHelpers::AlignDown(SIZE_T value, SIZE_T alignment)
+{
+ _ASSERTE((alignment & (alignment - 1)) == 0); // must be a power of 2
+ return value & ~(alignment - 1);
+}
+
+SIZE_T SharedMemoryHelpers::AlignUp(SIZE_T value, SIZE_T alignment)
+{
+ _ASSERTE((alignment & (alignment - 1)) == 0); // must be a power of 2
+ return AlignDown(value + (alignment - 1), alignment);
+}
+
+bool SharedMemoryHelpers::EnsureDirectoryExists(const char *path, bool isGlobalLockAcquired, bool createIfNotExist)
+{
+ _ASSERTE(path != nullptr);
+ _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
+
+ // Check if the path already exists
+ struct stat statInfo;
+ int statResult = stat(path, &statInfo);
+ if (statResult != 0 && errno == ENOENT)
+ {
+ if (!createIfNotExist)
+ {
+ return false;
+ }
+
+ // The path does not exist, create the directory. The permissions mask passed to mkdir() is filtered by the process'
+ // permissions umask, so mkdir() may not set all of the requested permissions. We need to use chmod() to set the proper
+ // permissions. That creates a race when there is no global lock acquired when creating the directory. Another user's
+ // process may create the directory and this user's process may try to use it before the other process sets the full
+ // permissions. In that case, create a temporary directory first, set the permissions, and rename it to the actual
+ // directory name.
+
+ if (isGlobalLockAcquired)
+ {
+ if (mkdir(path, PermissionsMask_AllUsers_ReadWriteExecute) != 0)
+ {
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
+ }
+ if (chmod(path, PermissionsMask_AllUsers_ReadWriteExecute) != 0)
+ {
+ rmdir(path);
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
+ }
+ return true;
+ }
+
+ char tempPath[] = SHARED_MEMORY_UNIQUE_TEMP_NAME_TEMPLATE;
+ if (mkdtemp(tempPath) == nullptr)
+ {
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
+ }
+ if (chmod(tempPath, PermissionsMask_AllUsers_ReadWriteExecute) != 0)
+ {
+ rmdir(tempPath);
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
+ }
+ if (rename(tempPath, path) == 0)
+ {
+ return true;
+ }
+
+ // Another process may have beaten us to it. Delete the temp directory and continue to check the requested directory to
+ // see if it meets our needs.
+ rmdir(tempPath);
+ statResult = stat(path, &statInfo);
+ }
+
+ // If the path exists, check that it's a directory
+ if (statResult != 0 || !(statInfo.st_mode & S_IFDIR))
+ {
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
+ }
+
+ // Check the directory's permissions and try to update them
+ if ((statInfo.st_mode & PermissionsMask_AllUsers_ReadWriteExecute) == PermissionsMask_AllUsers_ReadWriteExecute)
+ {
+ return true;
+ }
+ if (!createIfNotExist || chmod(path, PermissionsMask_AllUsers_ReadWriteExecute) != 0)
+ {
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
+ }
+ return true;
+}
+
+int SharedMemoryHelpers::Open(LPCSTR path, int flags, mode_t mode)
+{
+ int openErrorCode;
+ do
+ {
+ int fileDescriptor = InternalOpen(path, flags, mode);
+ if (fileDescriptor != -1)
+ {
+ return fileDescriptor;
+ }
+ openErrorCode = errno;
+ } while (openErrorCode == EINTR);
+
+ switch (openErrorCode)
+ {
+ case ENOENT:
+ _ASSERTE(!(flags & O_CREAT));
+ errno = openErrorCode;
+ return -1;
+
+ case ENAMETOOLONG:
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::NameTooLong));
+
+ case EMFILE:
+ case ENFILE:
+ case ENOMEM:
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::OutOfMemory));
+
+ default:
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
+ }
+}
+
+int SharedMemoryHelpers::OpenDirectory(LPCSTR path)
+{
+ _ASSERTE(path != nullptr);
+ _ASSERTE(path[0] != '\0');
+
+ int fileDescriptor = Open(path, O_RDONLY);
+ _ASSERTE(fileDescriptor != -1 || errno == ENOENT);
+ return fileDescriptor;
+}
+
+int SharedMemoryHelpers::CreateOrOpenFile(LPCSTR path, bool createIfNotExist, bool *createdRef)
+{
+ _ASSERTE(path != nullptr);
+ _ASSERTE(path[0] != '\0');
+
+ // Try to open the file
+ int openFlags = O_RDWR;
+ int fileDescriptor = Open(path, openFlags);
+ if (fileDescriptor != -1)
+ {
+ if (createdRef != nullptr)
+ {
+ *createdRef = false;
+ }
+ return fileDescriptor;
+ }
+ _ASSERTE(errno == ENOENT);
+ if (!createIfNotExist)
+ {
+ if (createdRef != nullptr)
+ {
+ *createdRef = false;
+ }
+ return -1;
+ }
+
+ // File does not exist, create the file
+ openFlags |= O_CREAT | O_EXCL;
+ fileDescriptor = Open(path, openFlags, PermissionsMask_AllUsers_ReadWrite);
+ _ASSERTE(fileDescriptor != -1);
+
+ // The permissions mask passed to open() is filtered by the process' permissions umask, so open() may not set all of
+ // the requested permissions. Use chmod() to set the proper permissions.
+ if (chmod(path, PermissionsMask_AllUsers_ReadWrite) != 0)
+ {
+ CloseFile(fileDescriptor);
+ unlink(path);
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
+ }
+
+ if (createdRef != nullptr)
+ {
+ *createdRef = true;
+ }
+ return fileDescriptor;
+}
+
+void SharedMemoryHelpers::CloseFile(int fileDescriptor)
+{
+ _ASSERTE(fileDescriptor != -1);
+
+ int closeResult;
+ do
+ {
+ closeResult = close(fileDescriptor);
+ } while (closeResult != 0 && errno == EINTR);
+}
+
+SIZE_T SharedMemoryHelpers::GetFileSize(int fileDescriptor)
+{
+ _ASSERTE(fileDescriptor != -1);
+
+ off_t endOffset = lseek(fileDescriptor, 0, SEEK_END);
+ if (endOffset == static_cast<off_t>(-1) ||
+ lseek(fileDescriptor, 0, SEEK_SET) == static_cast<off_t>(-1))
+ {
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
+ }
+ return endOffset;
+}
+
+void SharedMemoryHelpers::SetFileSize(int fileDescriptor, SIZE_T byteCount)
+{
+ _ASSERTE(fileDescriptor != -1);
+ _ASSERTE(static_cast<off_t>(byteCount) == byteCount);
+
+ while (true)
+ {
+ int ftruncateResult = ftruncate(fileDescriptor, static_cast<off_t>(byteCount));
+ if (ftruncateResult == 0)
+ {
+ break;
+ }
+ if (errno != EINTR)
+ {
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
+ }
+ }
+}
+
+void *SharedMemoryHelpers::MemoryMapFile(int fileDescriptor, SIZE_T byteCount)
+{
+ _ASSERTE(fileDescriptor != -1);
+ _ASSERTE(byteCount > sizeof(SharedMemorySharedDataHeader));
+ _ASSERTE(AlignDown(byteCount, VIRTUAL_PAGE_SIZE) == byteCount);
+
+ void *sharedMemoryBuffer = mmap(nullptr, byteCount, PROT_READ | PROT_WRITE, MAP_SHARED, fileDescriptor, 0);
+ if (sharedMemoryBuffer != MAP_FAILED)
+ {
+ return sharedMemoryBuffer;
+ }
+ switch (errno)
+ {
+ case ENFILE:
+ case ENOMEM:
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::OutOfMemory));
+
+ default:
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
+ }
+}
+
+bool SharedMemoryHelpers::TryAcquireFileLock(int fileDescriptor, int operation)
+{
+ // A file lock is acquired once per file descriptor, so the caller will need to synchronize threads of this process
+
+ _ASSERTE(fileDescriptor != -1);
+ _ASSERTE(!(operation & LOCK_UN));
+
+ while (true)
+ {
+ if (flock(fileDescriptor, operation) == 0)
+ {
+ return true;
+ }
+
+ int flockError = errno;
+ switch (flockError)
+ {
+ case EWOULDBLOCK:
+ return false;
+
+ case EINTR:
+ continue;
+
+ default:
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::OutOfMemory));
+ }
+ }
+}
+
+void SharedMemoryHelpers::ReleaseFileLock(int fileDescriptor)
+{
+ _ASSERTE(fileDescriptor != -1);
+
+ int flockResult;
+ do
+ {
+ flockResult = flock(fileDescriptor, LOCK_UN);
+ } while (flockResult != 0 && errno == EINTR);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// SharedMemoryId
+
+SharedMemoryId::SharedMemoryId() : m_name(nullptr)
+{
+}
+
+SharedMemoryId::SharedMemoryId(LPCSTR name, SIZE_T nameCharCount, bool isSessionScope)
+ : m_name(name), m_nameCharCount(nameCharCount), m_isSessionScope(isSessionScope)
+{
+ _ASSERTE(name != nullptr);
+ _ASSERTE(nameCharCount != 0);
+ _ASSERTE(nameCharCount <= SHARED_MEMORY_MAX_FILE_NAME_CHAR_COUNT);
+ _ASSERTE(strlen(name) == nameCharCount);
+}
+
+SharedMemoryId::SharedMemoryId(LPCSTR name)
+{
+ _ASSERTE(name != nullptr);
+
+ // Look for "Global\" and "Local\" prefixes in the name, and determine the session ID
+ if (strncmp(name, "Global\\", 7) == 0)
+ {
+ m_isSessionScope = false;
+ name += _countof("Global\\") - 1;
+ }
+ else
+ {
+ if (strncmp(name, "Local\\", 6) == 0)
+ {
+ name += _countof("Local\\") - 1;
+ }
+ m_isSessionScope = true;
+ }
+ m_name = name;
+
+ m_nameCharCount = strlen(name);
+ if (m_nameCharCount == 0)
+ {
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::NameEmpty));
+ }
+ if (m_nameCharCount > SHARED_MEMORY_MAX_FILE_NAME_CHAR_COUNT)
+ {
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::NameTooLong));
+ }
+
+ // Look for invalid characters '\' and '/' in the name
+ for (SIZE_T i = 0; i < m_nameCharCount; ++i)
+ {
+ char c = name[i];
+ if (c == '\\' || c == '/')
+ {
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::NameInvalid));
+ }
+ }
+}
+
+LPCSTR SharedMemoryId::GetName() const
+{
+ _ASSERTE(m_name != nullptr);
+ return m_name;
+}
+
+SIZE_T SharedMemoryId::GetNameCharCount() const
+{
+ _ASSERTE(m_name != nullptr);
+ return m_nameCharCount;
+}
+
+bool SharedMemoryId::IsSessionScope() const
+{
+ _ASSERTE(m_name != nullptr);
+ return m_isSessionScope;
+}
+
+bool SharedMemoryId::Equals(SharedMemoryId *other) const
+{
+ return
+ GetNameCharCount() == other->GetNameCharCount() &&
+ IsSessionScope() == other->IsSessionScope() &&
+ strcmp(GetName(), other->GetName()) == 0;
+}
+
+SIZE_T SharedMemoryId::AppendSessionDirectoryName(
+ char (&path)[SHARED_MEMORY_MAX_FILE_PATH_CHAR_COUNT + 1],
+ SIZE_T pathCharCount) const
+{
+ if (IsSessionScope())
+ {
+ pathCharCount = SharedMemoryHelpers::CopyString(path, pathCharCount, SHARED_MEMORY_SESSION_DIRECTORY_NAME_PREFIX);
+ pathCharCount = SharedMemoryHelpers::AppendUInt32String(path, pathCharCount, GetCurrentSessionId());
+ }
+ else
+ {
+ pathCharCount = SharedMemoryHelpers::CopyString(path, pathCharCount, SHARED_MEMORY_GLOBAL_DIRECTORY_NAME);
+ }
+
+ _ASSERTE(pathCharCount <= SHARED_MEMORY_MAX_FILE_PATH_CHAR_COUNT);
+ return pathCharCount;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// SharedMemorySharedDataHeader
+
+SIZE_T SharedMemorySharedDataHeader::DetermineTotalByteCount(SIZE_T dataByteCount)
+{
+ return SharedMemoryHelpers::AlignUp(sizeof(SharedMemorySharedDataHeader) + dataByteCount, VIRTUAL_PAGE_SIZE);
+}
+
+SharedMemorySharedDataHeader::SharedMemorySharedDataHeader(SharedMemoryType type, UINT8 version)
+ : m_type(type), m_version(version)
+{
+}
+
+SharedMemoryType SharedMemorySharedDataHeader::GetType() const
+{
+ return m_type;
+}
+
+UINT8 SharedMemorySharedDataHeader::GetVersion() const
+{
+ return m_version;
+}
+
+void *SharedMemorySharedDataHeader::GetData()
+{
+ return this + 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// SharedMemoryProcessDataHeader
+
+SharedMemoryProcessDataHeader *SharedMemoryProcessDataHeader::CreateOrOpen(
+ LPCSTR name,
+ SharedMemorySharedDataHeader requiredSharedDataHeader,
+ SIZE_T sharedDataByteCount,
+ bool createIfNotExist,
+ bool *createdRef)
+{
+ _ASSERTE(name != nullptr);
+ _ASSERTE(sharedDataByteCount != 0);
+ _ASSERTE(!createIfNotExist || createdRef != nullptr);
+ _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
+ _ASSERTE(!SharedMemoryManager::IsCreationDeletionFileLockAcquired());
+
+ if (createdRef != nullptr)
+ {
+ *createdRef = false;
+ }
+
+ SharedMemoryId id(name);
+
+ struct AutoCleanup
+ {
+ bool m_acquiredCreationDeletionFileLock;
+ char *m_filePath;
+ SIZE_T m_sessionDirectoryPathCharCount;
+ bool m_createdFile;
+ int m_fileDescriptor;
+ bool m_acquiredFileLock;
+ void *m_mappedBuffer;
+ SIZE_T m_mappedBufferByteCount;
+ bool m_cancel;
+
+ AutoCleanup()
+ : m_acquiredCreationDeletionFileLock(false),
+ m_filePath(nullptr),
+ m_sessionDirectoryPathCharCount(0),
+ m_createdFile(false),
+ m_fileDescriptor(-1),
+ m_acquiredFileLock(false),
+ m_mappedBuffer(nullptr),
+ m_mappedBufferByteCount(0),
+ m_cancel(false)
+ {
+ }
+
+ ~AutoCleanup()
+ {
+ if (m_cancel)
+ {
+ return;
+ }
+
+ if (m_mappedBuffer != nullptr)
+ {
+ _ASSERTE(m_mappedBufferByteCount != 0);
+ munmap(m_mappedBuffer, m_mappedBufferByteCount);
+ }
+
+ if (m_acquiredFileLock)
+ {
+ _ASSERTE(m_fileDescriptor != -1);
+ SharedMemoryHelpers::ReleaseFileLock(m_fileDescriptor);
+ }
+
+ if (m_fileDescriptor != -1)
+ {
+ SharedMemoryHelpers::CloseFile(m_fileDescriptor);
+ }
+
+ if (m_createdFile)
+ {
+ _ASSERTE(m_filePath != nullptr);
+ unlink(m_filePath);
+ }
+
+ if (m_sessionDirectoryPathCharCount != 0)
+ {
+ _ASSERTE(m_filePath != nullptr);
+ m_filePath[m_sessionDirectoryPathCharCount] = '\0';
+ rmdir(m_filePath);
+ }
+
+ if (m_acquiredCreationDeletionFileLock)
+ {
+ SharedMemoryManager::ReleaseCreationDeletionFileLock();
+ }
+ }
+ } autoCleanup;
+
+ SharedMemoryProcessDataHeader *processDataHeader = SharedMemoryManager::FindProcessDataHeader(&id);
+ if (processDataHeader != nullptr)
+ {
+ _ASSERTE(
+ processDataHeader->GetSharedDataTotalByteCount() ==
+ SharedMemorySharedDataHeader::DetermineTotalByteCount(sharedDataByteCount));
+ processDataHeader->IncRefCount();
+ return processDataHeader;
+ }
+
+ SharedMemoryManager::AcquireCreationDeletionFileLock();
+ autoCleanup.m_acquiredCreationDeletionFileLock = true;
+
+ // Create the session directory
+ char filePath[SHARED_MEMORY_MAX_FILE_PATH_CHAR_COUNT + 1];
+ SIZE_T filePathCharCount = SharedMemoryHelpers::CopyString(filePath, 0, SHARED_MEMORY_SHARED_MEMORY_DIRECTORY_PATH);
+ filePath[filePathCharCount++] = '/';
+ filePathCharCount = id.AppendSessionDirectoryName(filePath, filePathCharCount);
+ if (!SharedMemoryHelpers::EnsureDirectoryExists(filePath, true /* isGlobalLockAcquired */, createIfNotExist))
+ {
+ _ASSERTE(!createIfNotExist);
+ return nullptr;
+ }
+ autoCleanup.m_filePath = filePath;
+ autoCleanup.m_sessionDirectoryPathCharCount = filePathCharCount;
+
+ // Create or open the shared memory file
+ filePath[filePathCharCount++] = '/';
+ filePathCharCount = SharedMemoryHelpers::CopyString(filePath, filePathCharCount, id.GetName(), id.GetNameCharCount());
+ bool createdFile;
+ int fileDescriptor = SharedMemoryHelpers::CreateOrOpenFile(filePath, createIfNotExist, &createdFile);
+ if (fileDescriptor == -1)
+ {
+ _ASSERTE(!createIfNotExist);
+ return nullptr;
+ }
+ autoCleanup.m_createdFile = createdFile;
+ autoCleanup.m_fileDescriptor = fileDescriptor;
+
+ bool clearContents = false;
+ if (!createdFile)
+ {
+ // A shared file lock on the shared memory file would be held by any process that has opened the same file. Try to take
+ // an exclusive lock on the file. Successfully acquiring an exclusive lock indicates that no process has a reference to
+ // the shared memory file, and this process can reinitialize its contents.
+ if (SharedMemoryHelpers::TryAcquireFileLock(fileDescriptor, LOCK_EX | LOCK_NB))
+ {
+ // The shared memory file is not being used, flag it as created so that its contents will be reinitialized
+ SharedMemoryHelpers::ReleaseFileLock(fileDescriptor);
+ autoCleanup.m_createdFile = true;
+ if (!createIfNotExist)
+ {
+ return nullptr;
+ }
+ createdFile = true;
+ clearContents = true;
+ }
+ }
+
+ // Set or validate the file length
+ SIZE_T sharedDataTotalByteCount = SharedMemorySharedDataHeader::DetermineTotalByteCount(sharedDataByteCount);
+ if (createdFile)
+ {
+ SharedMemoryHelpers::SetFileSize(fileDescriptor, sharedDataTotalByteCount);
+ }
+ else if (SharedMemoryHelpers::GetFileSize(fileDescriptor) != sharedDataTotalByteCount)
+ {
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::HeaderMismatch));
+ }
+
+ // Acquire and hold a shared file lock on the shared memory file as long as it is open, to indicate that this process is
+ // using the file. An exclusive file lock is attempted above to detect whether the file contents are valid, for the case
+ // where a process crashes or is killed after the file is created. Since we already hold the creation/deletion locks, a
+ // non-blocking file lock should succeed.
+ if (!SharedMemoryHelpers::TryAcquireFileLock(fileDescriptor, LOCK_SH | LOCK_NB))
+ {
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
+ }
+ autoCleanup.m_acquiredFileLock = true;
+
+ // Map the file into memory, and initialize or validate the header
+ void *mappedBuffer = SharedMemoryHelpers::MemoryMapFile(fileDescriptor, sharedDataTotalByteCount);
+ autoCleanup.m_mappedBuffer = mappedBuffer;
+ autoCleanup.m_mappedBufferByteCount = sharedDataTotalByteCount;
+ SharedMemorySharedDataHeader *sharedDataHeader;
+ if (createdFile)
+ {
+ if (clearContents)
+ {
+ memset(mappedBuffer, 0, sharedDataTotalByteCount);
+ }
+ sharedDataHeader = new(mappedBuffer) SharedMemorySharedDataHeader(requiredSharedDataHeader);
+ }
+ else
+ {
+ sharedDataHeader = reinterpret_cast<SharedMemorySharedDataHeader *>(mappedBuffer);
+ if (sharedDataHeader->GetType() != requiredSharedDataHeader.GetType() ||
+ sharedDataHeader->GetVersion() != requiredSharedDataHeader.GetVersion())
+ {
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::HeaderMismatch));
+ }
+ }
+
+ // When *createdRef is true, the creation/deletion file lock will remain locked upon returning for the caller to initialize
+ // the shared data. The caller must release the file lock afterwards.
+ if (!createdFile)
+ {
+ autoCleanup.m_acquiredCreationDeletionFileLock = false;
+ SharedMemoryManager::ReleaseCreationDeletionFileLock();
+ }
+
+ processDataHeader = SharedMemoryProcessDataHeader::New(&id, fileDescriptor, sharedDataHeader, sharedDataTotalByteCount);
+
+ autoCleanup.m_cancel = true;
+ if (createdFile)
+ {
+ _ASSERTE(createIfNotExist);
+ _ASSERTE(createdRef != nullptr);
+ *createdRef = true;
+ }
+ return processDataHeader;
+}
+
+SharedMemoryProcessDataHeader *SharedMemoryProcessDataHeader::PalObject_GetProcessDataHeader(CorUnix::IPalObject *object)
+{
+ _ASSERTE(object != nullptr);
+ _ASSERTE(object->GetObjectType()->GetId() == otiNamedMutex);
+ _ASSERTE(object->GetObjectType()->GetImmutableDataSize() == sizeof(SharedMemoryProcessDataHeader *));
+
+ void *immutableDataBuffer;
+ PAL_ERROR errorCode = object->GetImmutableData(&immutableDataBuffer);
+ _ASSERTE(errorCode == NO_ERROR);
+ _ASSERTE(immutableDataBuffer != nullptr);
+ return *reinterpret_cast<SharedMemoryProcessDataHeader **>(immutableDataBuffer);
+}
+
+void SharedMemoryProcessDataHeader::PalObject_SetProcessDataHeader(
+ CorUnix::IPalObject *object,
+ SharedMemoryProcessDataHeader *processDataHeader)
+{
+ _ASSERTE(object != nullptr);
+ _ASSERTE(object->GetObjectType()->GetId() == otiNamedMutex);
+ _ASSERTE(object->GetObjectType()->GetImmutableDataSize() == sizeof(SharedMemoryProcessDataHeader *));
+ _ASSERTE(processDataHeader != nullptr);
+
+ void *immutableDataBuffer;
+ PAL_ERROR errorCode = object->GetImmutableData(&immutableDataBuffer);
+ _ASSERTE(errorCode == NO_ERROR);
+ _ASSERTE(immutableDataBuffer != nullptr);
+ *reinterpret_cast<SharedMemoryProcessDataHeader **>(immutableDataBuffer) = processDataHeader;
+}
+
+void SharedMemoryProcessDataHeader::PalObject_Close(
+ CPalThread *thread,
+ IPalObject *object,
+ bool isShuttingDown,
+ bool cleanUpPalSharedState)
+{
+ // This function's signature matches OBJECTCLEANUPROUTINE
+ _ASSERTE(thread != nullptr);
+ _ASSERTE(object != nullptr);
+ _ASSERTE(object->GetObjectType()->GetId() == otiNamedMutex);
+ _ASSERTE(object->GetObjectType()->GetImmutableDataSize() == sizeof(SharedMemoryProcessDataHeader *));
+
+ SharedMemoryProcessDataHeader *processDataHeader = PalObject_GetProcessDataHeader(object);
+ if (processDataHeader == nullptr)
+ {
+ // The object was created, but an error must have occurred before the process data was initialized
+ return;
+ }
+
+ SharedMemoryManager::AcquireCreationDeletionProcessLock();
+ processDataHeader->DecRefCount();
+ SharedMemoryManager::ReleaseCreationDeletionProcessLock();
+}
+
+SharedMemoryProcessDataHeader::SharedMemoryProcessDataHeader(
+ SharedMemoryId *id,
+ int fileDescriptor,
+ SharedMemorySharedDataHeader *sharedDataHeader,
+ SIZE_T sharedDataTotalByteCount)
+ :
+ m_refCount(1),
+ m_data(nullptr),
+ m_fileDescriptor(fileDescriptor),
+ m_sharedDataHeader(sharedDataHeader),
+ m_sharedDataTotalByteCount(sharedDataTotalByteCount),
+ m_nextInProcessDataHeaderList(nullptr)
+{
+ _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
+ _ASSERTE(id != nullptr);
+ _ASSERTE(fileDescriptor != -1);
+ _ASSERTE(sharedDataHeader != nullptr);
+ _ASSERTE(sharedDataTotalByteCount > sizeof(SharedMemorySharedDataHeader));
+ _ASSERTE(SharedMemoryHelpers::AlignDown(sharedDataTotalByteCount, VIRTUAL_PAGE_SIZE) == sharedDataTotalByteCount);
+
+ // Copy the name and initialize the ID
+ char *nameCopy = reinterpret_cast<char *>(this + 1);
+ SIZE_T nameByteCount = id->GetNameCharCount() + 1;
+ memcpy_s(nameCopy, nameByteCount, id->GetName(), nameByteCount);
+ m_id = SharedMemoryId(nameCopy, id->GetNameCharCount(), id->IsSessionScope());
+
+ SharedMemoryManager::AddProcessDataHeader(this);
+}
+
+SharedMemoryProcessDataHeader *SharedMemoryProcessDataHeader::New(
+ SharedMemoryId *id,
+ int fileDescriptor,
+ SharedMemorySharedDataHeader *sharedDataHeader,
+ SIZE_T sharedDataTotalByteCount)
+{
+ _ASSERTE(id != nullptr);
+
+ // Allocate space for the header and a copy of the name
+ SIZE_T nameByteCount = id->GetNameCharCount() + 1;
+ SIZE_T totalByteCount = sizeof(SharedMemoryProcessDataHeader) + nameByteCount;
+ void *buffer = SharedMemoryHelpers::Alloc(totalByteCount);
+ AutoFreeBuffer autoFreeBuffer(buffer);
+ SharedMemoryProcessDataHeader *processDataHeader =
+ new(buffer) SharedMemoryProcessDataHeader(id, fileDescriptor, sharedDataHeader, sharedDataTotalByteCount);
+ autoFreeBuffer.Cancel();
+ return processDataHeader;
+}
+
+SharedMemoryProcessDataHeader::~SharedMemoryProcessDataHeader()
+{
+ _ASSERTE(m_refCount == 0);
+ Close();
+}
+
+void SharedMemoryProcessDataHeader::Close()
+{
+ _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
+ _ASSERTE(!SharedMemoryManager::IsCreationDeletionFileLockAcquired());
+
+ // If the ref count is nonzero, we are shutting down the process abruptly without having closed some shared memory objects.
+ // There could still be threads running with active references to the shared memory object. So when the ref count is
+ // nonzero, don't clean up any object or global process-local state.
+ if (m_refCount == 0)
+ {
+ SharedMemoryManager::RemoveProcessDataHeader(this);
+ }
+
+ struct AutoReleaseCreationDeletionFileLock
+ {
+ bool m_acquired;
+
+ AutoReleaseCreationDeletionFileLock() : m_acquired(false)
+ {
+ }
+
+ ~AutoReleaseCreationDeletionFileLock()
+ {
+ if (m_acquired)
+ {
+ SharedMemoryManager::ReleaseCreationDeletionFileLock();
+ }
+ }
+ } autoReleaseCreationDeletionFileLock;
+
+ // A shared file lock on the shared memory file would be held by any process that has opened the same file. Try to take
+ // an exclusive lock on the file. Successfully acquiring an exclusive lock indicates that no process has a reference to
+ // the shared memory file, and this process can delete the file. File locks on the shared memory file are only ever acquired
+ // or released while holding the creation/deletion locks, so holding the creation/deletion locks while trying an exclusive
+ // lock on the shared memory file guarantees that another process cannot start using the shared memory file after this
+ // process has decided to delete the file.
+ bool releaseSharedData = false;
+ try
+ {
+ SharedMemoryManager::AcquireCreationDeletionFileLock();
+ autoReleaseCreationDeletionFileLock.m_acquired = true;
+
+ SharedMemoryHelpers::ReleaseFileLock(m_fileDescriptor);
+ if (SharedMemoryHelpers::TryAcquireFileLock(m_fileDescriptor, LOCK_EX | LOCK_NB))
+ {
+ SharedMemoryHelpers::ReleaseFileLock(m_fileDescriptor);
+ releaseSharedData = true;
+ }
+ }
+ catch (SharedMemoryException)
+ {
+ // Ignore the error, just don't release shared data
+ }
+
+ if (m_data != nullptr)
+ {
+ m_data->Close(m_refCount != 0 /* isAbruptShutdown */, releaseSharedData);
+ }
+
+ if (m_refCount == 0)
+ {
+ if (m_data != nullptr)
+ {
+ InternalDelete(m_data);
+ }
+
+ if (releaseSharedData)
+ {
+ m_sharedDataHeader->~SharedMemorySharedDataHeader();
+ }
+
+ munmap(m_sharedDataHeader, m_sharedDataTotalByteCount);
+ SharedMemoryHelpers::CloseFile(m_fileDescriptor);
+ }
+
+ if (!releaseSharedData)
+ {
+ return;
+ }
+
+ // Delete the shared memory file, and the session directory if it's not empty
+ char path[SHARED_MEMORY_MAX_FILE_PATH_CHAR_COUNT + 1];
+ SIZE_T sessionDirectoryPathCharCount = SharedMemoryHelpers::CopyString(path, 0, SHARED_MEMORY_SHARED_MEMORY_DIRECTORY_PATH);
+ path[sessionDirectoryPathCharCount++] = '/';
+ sessionDirectoryPathCharCount = m_id.AppendSessionDirectoryName(path, sessionDirectoryPathCharCount);
+ path[sessionDirectoryPathCharCount++] = '/';
+ SharedMemoryHelpers::CopyString(path, sessionDirectoryPathCharCount, m_id.GetName(), m_id.GetNameCharCount());
+ unlink(path);
+ path[sessionDirectoryPathCharCount] = '\0';
+ rmdir(path);
+}
+
+SharedMemoryId *SharedMemoryProcessDataHeader::GetId()
+{
+ return &m_id;
+}
+
+SharedMemoryProcessDataBase *SharedMemoryProcessDataHeader::GetData() const
+{
+ return m_data;
+}
+
+void SharedMemoryProcessDataHeader::SetData(SharedMemoryProcessDataBase *data)
+{
+ _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
+ _ASSERTE(m_data == nullptr);
+ _ASSERTE(data != nullptr);
+
+ m_data = data;
+}
+
+SharedMemorySharedDataHeader *SharedMemoryProcessDataHeader::GetSharedDataHeader() const
+{
+ return m_sharedDataHeader;
+}
+
+SIZE_T SharedMemoryProcessDataHeader::GetSharedDataTotalByteCount() const
+{
+ return m_sharedDataTotalByteCount;
+}
+
+SharedMemoryProcessDataHeader *SharedMemoryProcessDataHeader::GetNextInProcessDataHeaderList() const
+{
+ _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
+ return m_nextInProcessDataHeaderList;
+}
+
+void SharedMemoryProcessDataHeader::SetNextInProcessDataHeaderList(SharedMemoryProcessDataHeader *next)
+{
+ _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
+ m_nextInProcessDataHeaderList = next;
+}
+
+void SharedMemoryProcessDataHeader::IncRefCount()
+{
+ _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
+ _ASSERTE(m_refCount != 0);
+
+ ++m_refCount;
+}
+
+void SharedMemoryProcessDataHeader::DecRefCount()
+{
+ _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
+ _ASSERTE(m_refCount != 0);
+
+ if (--m_refCount == 0)
+ {
+ InternalDelete(this);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// SharedMemoryManager
+
+CRITICAL_SECTION SharedMemoryManager::s_creationDeletionProcessLock;
+int SharedMemoryManager::s_creationDeletionLockFileDescriptor = -1;
+
+SharedMemoryProcessDataHeader *SharedMemoryManager::s_processDataHeaderListHead = nullptr;
+
+#ifdef _DEBUG
+SIZE_T SharedMemoryManager::s_creationDeletionProcessLockOwnerThreadId = SharedMemoryHelpers::InvalidThreadId;
+SIZE_T SharedMemoryManager::s_creationDeletionFileLockOwnerThreadId = SharedMemoryHelpers::InvalidThreadId;
+#endif // _DEBUG
+
+void SharedMemoryManager::StaticInitialize()
+{
+ InitializeCriticalSection(&s_creationDeletionProcessLock);
+}
+
+void SharedMemoryManager::StaticClose()
+{
+ // This function could very well be running during abrupt shutdown, and there could still be user threads running.
+ // Synchronize the deletion, and don't remove or delete items in the linked list.
+ AcquireCreationDeletionProcessLock();
+ for (SharedMemoryProcessDataHeader *current = s_processDataHeaderListHead;
+ current != nullptr;
+ current = current->GetNextInProcessDataHeaderList())
+ {
+ current->Close();
+ }
+ ReleaseCreationDeletionProcessLock();
+
+ // This function could very well be running during abrupt shutdown, and there could still be user threads running. Don't
+ // delete the creation/deletion process lock, the process is shutting down anyway.
+}
+
+void SharedMemoryManager::AcquireCreationDeletionProcessLock()
+{
+ _ASSERTE(!IsCreationDeletionProcessLockAcquired());
+ _ASSERTE(!IsCreationDeletionFileLockAcquired());
+
+ EnterCriticalSection(&s_creationDeletionProcessLock);
+#ifdef _DEBUG
+ s_creationDeletionProcessLockOwnerThreadId = THREADSilentGetCurrentThreadId();
+#endif // _DEBUG
+}
+
+void SharedMemoryManager::ReleaseCreationDeletionProcessLock()
+{
+ _ASSERTE(IsCreationDeletionProcessLockAcquired());
+ _ASSERTE(!IsCreationDeletionFileLockAcquired());
+
+#ifdef _DEBUG
+ s_creationDeletionProcessLockOwnerThreadId = SharedMemoryHelpers::InvalidThreadId;
+#endif // _DEBUG
+ LeaveCriticalSection(&s_creationDeletionProcessLock);
+}
+
+void SharedMemoryManager::AcquireCreationDeletionFileLock()
+{
+ _ASSERTE(IsCreationDeletionProcessLockAcquired());
+ _ASSERTE(!IsCreationDeletionFileLockAcquired());
+
+ if (s_creationDeletionLockFileDescriptor == -1)
+ {
+ if (!SharedMemoryHelpers::EnsureDirectoryExists(
+ SHARED_MEMORY_TEMP_DIRECTORY_PATH,
+ false /* isGlobalLockAcquired */,
+ false /* createIfNotExist */))
+ {
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
+ }
+ SharedMemoryHelpers::EnsureDirectoryExists(
+ SHARED_MEMORY_RUNTIME_TEMP_DIRECTORY_PATH,
+ false /* isGlobalLockAcquired */);
+ SharedMemoryHelpers::EnsureDirectoryExists(
+ SHARED_MEMORY_SHARED_MEMORY_DIRECTORY_PATH,
+ false /* isGlobalLockAcquired */);
+ s_creationDeletionLockFileDescriptor = SharedMemoryHelpers::OpenDirectory(SHARED_MEMORY_SHARED_MEMORY_DIRECTORY_PATH);
+ if (s_creationDeletionLockFileDescriptor == -1)
+ {
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
+ }
+ }
+
+ bool acquiredFileLock = SharedMemoryHelpers::TryAcquireFileLock(s_creationDeletionLockFileDescriptor, LOCK_EX);
+ _ASSERTE(acquiredFileLock);
+#ifdef _DEBUG
+ s_creationDeletionFileLockOwnerThreadId = THREADSilentGetCurrentThreadId();
+#endif // _DEBUG
+}
+
+void SharedMemoryManager::ReleaseCreationDeletionFileLock()
+{
+ _ASSERTE(IsCreationDeletionProcessLockAcquired());
+ _ASSERTE(IsCreationDeletionFileLockAcquired());
+ _ASSERTE(s_creationDeletionLockFileDescriptor != -1);
+
+#ifdef _DEBUG
+ s_creationDeletionFileLockOwnerThreadId = SharedMemoryHelpers::InvalidThreadId;
+#endif // _DEBUG
+ SharedMemoryHelpers::ReleaseFileLock(s_creationDeletionLockFileDescriptor);
+}
+
+#ifdef _DEBUG
+bool SharedMemoryManager::IsCreationDeletionProcessLockAcquired()
+{
+ return s_creationDeletionProcessLockOwnerThreadId == THREADSilentGetCurrentThreadId();
+}
+
+bool SharedMemoryManager::IsCreationDeletionFileLockAcquired()
+{
+ return s_creationDeletionFileLockOwnerThreadId == THREADSilentGetCurrentThreadId();
+}
+#endif // _DEBUG
+
+void SharedMemoryManager::AddProcessDataHeader(SharedMemoryProcessDataHeader *processDataHeader)
+{
+ _ASSERTE(processDataHeader != nullptr);
+ _ASSERTE(IsCreationDeletionProcessLockAcquired());
+ _ASSERTE(processDataHeader->GetNextInProcessDataHeaderList() == nullptr);
+ _ASSERTE(FindProcessDataHeader(processDataHeader->GetId()) == nullptr);
+
+ processDataHeader->SetNextInProcessDataHeaderList(s_processDataHeaderListHead);
+ s_processDataHeaderListHead = processDataHeader;
+}
+
+void SharedMemoryManager::RemoveProcessDataHeader(SharedMemoryProcessDataHeader *processDataHeader)
+{
+ _ASSERTE(processDataHeader != nullptr);
+ _ASSERTE(IsCreationDeletionProcessLockAcquired());
+
+ if (s_processDataHeaderListHead == processDataHeader)
+ {
+ s_processDataHeaderListHead = processDataHeader->GetNextInProcessDataHeaderList();
+ processDataHeader->SetNextInProcessDataHeaderList(nullptr);
+ return;
+ }
+ for (SharedMemoryProcessDataHeader
+ *previous = s_processDataHeaderListHead,
+ *current = previous->GetNextInProcessDataHeaderList();
+ current != nullptr;
+ previous = current, current = current->GetNextInProcessDataHeaderList())
+ {
+ if (current == processDataHeader)
+ {
+ previous->SetNextInProcessDataHeaderList(current->GetNextInProcessDataHeaderList());
+ current->SetNextInProcessDataHeaderList(nullptr);
+ return;
+ }
+ }
+ _ASSERTE(false);
+}
+
+SharedMemoryProcessDataHeader *SharedMemoryManager::FindProcessDataHeader(SharedMemoryId *id)
+{
+ _ASSERTE(IsCreationDeletionProcessLockAcquired());
+
+ // TODO: Use a hash table
+ for (SharedMemoryProcessDataHeader *current = s_processDataHeaderListHead;
+ current != nullptr;
+ current = current->GetNextInProcessDataHeaderList())
+ {
+ if (current->GetId()->Equals(id))
+ {
+ return current;
+ }
+ }
+ return nullptr;
+}