From 4b4aad7217d3292650e77eec2cf4c198ea9c3b4b Mon Sep 17 00:00:00 2001 From: Jiyoung Yun Date: Wed, 23 Nov 2016 19:09:09 +0900 Subject: Imported Upstream version 1.1.0 --- src/pal/src/sharedmemory/sharedmemory.cpp | 1136 +++++++++++++++++++++++++++++ 1 file changed, 1136 insertions(+) create mode 100644 src/pal/src/sharedmemory/sharedmemory.cpp (limited to 'src/pal/src/sharedmemory/sharedmemory.cpp') 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 +#include +#include +#include + +#include +#include +#include +#include +#include + +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(-1); +const SIZE_T SharedMemoryHelpers::InvalidThreadId = static_cast(-1); +const UINT64 SharedMemoryHelpers::InvalidSharedThreadId = static_cast(-1); + +void *SharedMemoryHelpers::Alloc(SIZE_T byteCount) +{ + void *buffer = InternalMalloc(byteCount); + if (buffer == nullptr) + { + throw SharedMemoryException(static_cast(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(SharedMemoryError::IO)); + } + if (chmod(path, PermissionsMask_AllUsers_ReadWriteExecute) != 0) + { + rmdir(path); + throw SharedMemoryException(static_cast(SharedMemoryError::IO)); + } + return true; + } + + char tempPath[] = SHARED_MEMORY_UNIQUE_TEMP_NAME_TEMPLATE; + if (mkdtemp(tempPath) == nullptr) + { + throw SharedMemoryException(static_cast(SharedMemoryError::IO)); + } + if (chmod(tempPath, PermissionsMask_AllUsers_ReadWriteExecute) != 0) + { + rmdir(tempPath); + throw SharedMemoryException(static_cast(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(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(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(SharedMemoryError::NameTooLong)); + + case EMFILE: + case ENFILE: + case ENOMEM: + throw SharedMemoryException(static_cast(SharedMemoryError::OutOfMemory)); + + default: + throw SharedMemoryException(static_cast(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(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(-1) || + lseek(fileDescriptor, 0, SEEK_SET) == static_cast(-1)) + { + throw SharedMemoryException(static_cast(SharedMemoryError::IO)); + } + return endOffset; +} + +void SharedMemoryHelpers::SetFileSize(int fileDescriptor, SIZE_T byteCount) +{ + _ASSERTE(fileDescriptor != -1); + _ASSERTE(static_cast(byteCount) == byteCount); + + while (true) + { + int ftruncateResult = ftruncate(fileDescriptor, static_cast(byteCount)); + if (ftruncateResult == 0) + { + break; + } + if (errno != EINTR) + { + throw SharedMemoryException(static_cast(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(SharedMemoryError::OutOfMemory)); + + default: + throw SharedMemoryException(static_cast(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(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(SharedMemoryError::NameEmpty)); + } + if (m_nameCharCount > SHARED_MEMORY_MAX_FILE_NAME_CHAR_COUNT) + { + throw SharedMemoryException(static_cast(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(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(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(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(mappedBuffer); + if (sharedDataHeader->GetType() != requiredSharedDataHeader.GetType() || + sharedDataHeader->GetVersion() != requiredSharedDataHeader.GetVersion()) + { + throw SharedMemoryException(static_cast(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(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(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(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(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(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; +} -- cgit v1.2.3