1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4
5#include "pal/sharedmemory.h"
6
7#include "pal/dbgmsg.h"
8#include "pal/file.hpp"
9#include "pal/malloc.hpp"
10#include "pal/thread.hpp"
11#include "pal/virtual.h"
12#include "pal/process.h"
13
14#include <sys/file.h>
15#include <sys/mman.h>
16#include <sys/stat.h>
17#include <sys/types.h>
18
19#include <fcntl.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23#include <unistd.h>
24
25using namespace CorUnix;
26
27SET_DEFAULT_DEBUG_CHANNEL(SHMEM);
28
29#include "pal/sharedmemory.inl"
30
31////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
32// AutoFreeBuffer
33
34AutoFreeBuffer::AutoFreeBuffer(void *buffer) : m_buffer(buffer), m_cancel(false)
35{
36}
37
38AutoFreeBuffer::~AutoFreeBuffer()
39{
40 if (!m_cancel && m_buffer != nullptr)
41 {
42 free(m_buffer);
43 }
44}
45
46void AutoFreeBuffer::Cancel()
47{
48 m_cancel = true;
49}
50
51////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
52// SharedMemoryException
53
54SharedMemoryException::SharedMemoryException(DWORD errorCode) : m_errorCode(errorCode)
55{
56}
57
58DWORD SharedMemoryException::GetErrorCode() const
59{
60 return m_errorCode;
61}
62
63////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
64// SharedMemoryHelpers
65
66const mode_t SharedMemoryHelpers::PermissionsMask_CurrentUser_ReadWriteExecute = S_IRUSR | S_IWUSR | S_IXUSR;
67const mode_t SharedMemoryHelpers::PermissionsMask_AllUsers_ReadWrite =
68 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
69const mode_t SharedMemoryHelpers::PermissionsMask_AllUsers_ReadWriteExecute =
70 PermissionsMask_AllUsers_ReadWrite | (S_IXUSR | S_IXGRP | S_IXOTH);
71const UINT32 SharedMemoryHelpers::InvalidProcessId = static_cast<UINT32>(-1);
72const SIZE_T SharedMemoryHelpers::InvalidThreadId = static_cast<SIZE_T>(-1);
73const UINT64 SharedMemoryHelpers::InvalidSharedThreadId = static_cast<UINT64>(-1);
74
75void *SharedMemoryHelpers::Alloc(SIZE_T byteCount)
76{
77 void *buffer = InternalMalloc(byteCount);
78 if (buffer == nullptr)
79 {
80 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::OutOfMemory));
81 }
82 return buffer;
83}
84
85SIZE_T SharedMemoryHelpers::AlignDown(SIZE_T value, SIZE_T alignment)
86{
87 _ASSERTE((alignment & (alignment - 1)) == 0); // must be a power of 2
88 return value & ~(alignment - 1);
89}
90
91SIZE_T SharedMemoryHelpers::AlignUp(SIZE_T value, SIZE_T alignment)
92{
93 _ASSERTE((alignment & (alignment - 1)) == 0); // must be a power of 2
94 return AlignDown(value + (alignment - 1), alignment);
95}
96
97bool SharedMemoryHelpers::EnsureDirectoryExists(
98 const char *path,
99 bool isGlobalLockAcquired,
100 bool createIfNotExist,
101 bool isSystemDirectory)
102{
103 _ASSERTE(path != nullptr);
104 _ASSERTE(!(isSystemDirectory && createIfNotExist)); // should not create or change permissions on system directories
105 _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
106 _ASSERTE(!isGlobalLockAcquired || SharedMemoryManager::IsCreationDeletionFileLockAcquired());
107
108 // Check if the path already exists
109 struct stat statInfo;
110 int statResult = stat(path, &statInfo);
111 if (statResult != 0 && errno == ENOENT)
112 {
113 if (!createIfNotExist)
114 {
115 return false;
116 }
117
118 // The path does not exist, create the directory. The permissions mask passed to mkdir() is filtered by the process'
119 // permissions umask, so mkdir() may not set all of the requested permissions. We need to use chmod() to set the proper
120 // permissions. That creates a race when there is no global lock acquired when creating the directory. Another user's
121 // process may create the directory and this user's process may try to use it before the other process sets the full
122 // permissions. In that case, create a temporary directory first, set the permissions, and rename it to the actual
123 // directory name.
124
125 if (isGlobalLockAcquired)
126 {
127 if (mkdir(path, PermissionsMask_AllUsers_ReadWriteExecute) != 0)
128 {
129 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
130 }
131 if (chmod(path, PermissionsMask_AllUsers_ReadWriteExecute) != 0)
132 {
133 rmdir(path);
134 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
135 }
136 return true;
137 }
138
139 PathCharString tempPath;
140 BuildSharedFilesPath(tempPath, SHARED_MEMORY_UNIQUE_TEMP_NAME_TEMPLATE);
141
142 if (mkdtemp(tempPath.OpenStringBuffer()) == nullptr)
143 {
144 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
145 }
146 if (chmod(tempPath, PermissionsMask_AllUsers_ReadWriteExecute) != 0)
147 {
148 rmdir(tempPath);
149 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
150 }
151 if (rename(tempPath, path) == 0)
152 {
153 return true;
154 }
155
156 // Another process may have beaten us to it. Delete the temp directory and continue to check the requested directory to
157 // see if it meets our needs.
158 rmdir(tempPath);
159 statResult = stat(path, &statInfo);
160 }
161
162 // If the path exists, check that it's a directory
163 if (statResult != 0 || !(statInfo.st_mode & S_IFDIR))
164 {
165 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
166 }
167
168 if (isSystemDirectory)
169 {
170 // For system directories (such as TEMP_DIRECTORY_PATH), require sufficient permissions only for the
171 // current user. For instance, "docker run --mount ..." to mount /tmp to some directory on the host mounts the
172 // destination directory with the same permissions as the source directory, which may not include some permissions for
173 // other users. In the docker container, other user permissions are typically not relevant and relaxing the permissions
174 // requirement allows for that scenario to work without having to work around it by first giving sufficient permissions
175 // for all users.
176 if ((statInfo.st_mode & PermissionsMask_CurrentUser_ReadWriteExecute) == PermissionsMask_CurrentUser_ReadWriteExecute)
177 {
178 return true;
179 }
180 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
181 }
182
183 // For non-system directories (such as gSharedFilesPath/SHARED_MEMORY_RUNTIME_TEMP_DIRECTORY_NAME),
184 // require sufficient permissions for all users and try to update them if requested to create the directory, so that
185 // shared memory files may be shared by all processes on the system.
186 if ((statInfo.st_mode & PermissionsMask_AllUsers_ReadWriteExecute) == PermissionsMask_AllUsers_ReadWriteExecute)
187 {
188 return true;
189 }
190 if (!createIfNotExist || chmod(path, PermissionsMask_AllUsers_ReadWriteExecute) != 0)
191 {
192 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
193 }
194 return true;
195}
196
197int SharedMemoryHelpers::Open(LPCSTR path, int flags, mode_t mode)
198{
199 int openErrorCode;
200
201 flags |= O_CLOEXEC;
202 do
203 {
204 int fileDescriptor = InternalOpen(path, flags, mode);
205 if (fileDescriptor != -1)
206 {
207 return fileDescriptor;
208 }
209 openErrorCode = errno;
210 } while (openErrorCode == EINTR);
211
212 switch (openErrorCode)
213 {
214 case ENOENT:
215 _ASSERTE(!(flags & O_CREAT));
216 errno = openErrorCode;
217 return -1;
218
219 case ENAMETOOLONG:
220 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::NameTooLong));
221
222 case EMFILE:
223 case ENFILE:
224 case ENOMEM:
225 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::OutOfMemory));
226
227 default:
228 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
229 }
230}
231
232int SharedMemoryHelpers::OpenDirectory(LPCSTR path)
233{
234 _ASSERTE(path != nullptr);
235 _ASSERTE(path[0] != '\0');
236
237 int fileDescriptor = Open(path, O_RDONLY);
238 _ASSERTE(fileDescriptor != -1 || errno == ENOENT);
239 return fileDescriptor;
240}
241
242int SharedMemoryHelpers::CreateOrOpenFile(LPCSTR path, bool createIfNotExist, bool *createdRef)
243{
244 _ASSERTE(path != nullptr);
245 _ASSERTE(path[0] != '\0');
246 _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
247 _ASSERTE(!createIfNotExist || SharedMemoryManager::IsCreationDeletionFileLockAcquired());
248
249 // Try to open the file
250 int openFlags = O_RDWR;
251 int fileDescriptor = Open(path, openFlags);
252 if (fileDescriptor != -1)
253 {
254 if (createdRef != nullptr)
255 {
256 *createdRef = false;
257 }
258 return fileDescriptor;
259 }
260 _ASSERTE(errno == ENOENT);
261 if (!createIfNotExist)
262 {
263 if (createdRef != nullptr)
264 {
265 *createdRef = false;
266 }
267 return -1;
268 }
269
270 // File does not exist, create the file
271 openFlags |= O_CREAT | O_EXCL;
272 fileDescriptor = Open(path, openFlags, PermissionsMask_AllUsers_ReadWrite);
273 _ASSERTE(fileDescriptor != -1);
274
275 // The permissions mask passed to open() is filtered by the process' permissions umask, so open() may not set all of
276 // the requested permissions. Use chmod() to set the proper permissions.
277 if (chmod(path, PermissionsMask_AllUsers_ReadWrite) != 0)
278 {
279 CloseFile(fileDescriptor);
280 unlink(path);
281 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
282 }
283
284 if (createdRef != nullptr)
285 {
286 *createdRef = true;
287 }
288 return fileDescriptor;
289}
290
291void SharedMemoryHelpers::CloseFile(int fileDescriptor)
292{
293 _ASSERTE(fileDescriptor != -1);
294
295 int closeResult;
296 do
297 {
298 closeResult = close(fileDescriptor);
299 } while (closeResult != 0 && errno == EINTR);
300}
301
302SIZE_T SharedMemoryHelpers::GetFileSize(int fileDescriptor)
303{
304 _ASSERTE(fileDescriptor != -1);
305
306 off_t endOffset = lseek(fileDescriptor, 0, SEEK_END);
307 if (endOffset == static_cast<off_t>(-1) ||
308 lseek(fileDescriptor, 0, SEEK_SET) == static_cast<off_t>(-1))
309 {
310 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
311 }
312 return endOffset;
313}
314
315void SharedMemoryHelpers::SetFileSize(int fileDescriptor, SIZE_T byteCount)
316{
317 _ASSERTE(fileDescriptor != -1);
318 _ASSERTE(static_cast<off_t>(byteCount) == byteCount);
319
320 while (true)
321 {
322 int ftruncateResult = ftruncate(fileDescriptor, static_cast<off_t>(byteCount));
323 if (ftruncateResult == 0)
324 {
325 break;
326 }
327 if (errno != EINTR)
328 {
329 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
330 }
331 }
332}
333
334void *SharedMemoryHelpers::MemoryMapFile(int fileDescriptor, SIZE_T byteCount)
335{
336 _ASSERTE(fileDescriptor != -1);
337 _ASSERTE(byteCount > sizeof(SharedMemorySharedDataHeader));
338 _ASSERTE(AlignDown(byteCount, GetVirtualPageSize()) == byteCount);
339
340 void *sharedMemoryBuffer = mmap(nullptr, byteCount, PROT_READ | PROT_WRITE, MAP_SHARED, fileDescriptor, 0);
341 if (sharedMemoryBuffer != MAP_FAILED)
342 {
343 return sharedMemoryBuffer;
344 }
345 switch (errno)
346 {
347 case ENFILE:
348 case ENOMEM:
349 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::OutOfMemory));
350
351 default:
352 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
353 }
354}
355
356bool SharedMemoryHelpers::TryAcquireFileLock(int fileDescriptor, int operation)
357{
358 // A file lock is acquired once per file descriptor, so the caller will need to synchronize threads of this process
359
360 _ASSERTE(fileDescriptor != -1);
361 _ASSERTE(!(operation & LOCK_UN));
362
363 while (true)
364 {
365 if (flock(fileDescriptor, operation) == 0)
366 {
367 return true;
368 }
369
370 int flockError = errno;
371 switch (flockError)
372 {
373 case EWOULDBLOCK:
374 return false;
375
376 case EINTR:
377 continue;
378
379 default:
380 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::OutOfMemory));
381 }
382 }
383}
384
385void SharedMemoryHelpers::ReleaseFileLock(int fileDescriptor)
386{
387 _ASSERTE(fileDescriptor != -1);
388
389 int flockResult;
390 do
391 {
392 flockResult = flock(fileDescriptor, LOCK_UN);
393 } while (flockResult != 0 && errno == EINTR);
394}
395
396void SharedMemoryHelpers::BuildSharedFilesPath(PathCharString& destination, const char *suffix, int suffixCharCount)
397{
398 _ASSERTE(strlen(suffix) == suffixCharCount);
399
400 VerifyStringOperation(destination.Set(*gSharedFilesPath));
401 VerifyStringOperation(destination.Append(suffix, suffixCharCount));
402}
403
404bool SharedMemoryHelpers::AppendUInt32String(
405 PathCharString& destination,
406 UINT32 value)
407{
408 char int32String[16];
409
410 int valueCharCount =
411 sprintf_s(int32String, sizeof(int32String), "%u", value);
412 _ASSERTE(valueCharCount > 0);
413 return destination.Append(int32String, valueCharCount) != FALSE;
414}
415
416void SharedMemoryHelpers::VerifyStringOperation(bool success)
417{
418 if (!success)
419 {
420 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::OutOfMemory));
421 }
422}
423
424////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
425// SharedMemoryId
426
427SharedMemoryId::SharedMemoryId() : m_name(nullptr)
428{
429}
430
431SharedMemoryId::SharedMemoryId(LPCSTR name, SIZE_T nameCharCount, bool isSessionScope)
432 : m_name(name), m_nameCharCount(nameCharCount), m_isSessionScope(isSessionScope)
433{
434 _ASSERTE(name != nullptr);
435 _ASSERTE(nameCharCount != 0);
436 _ASSERTE(nameCharCount <= SHARED_MEMORY_MAX_FILE_NAME_CHAR_COUNT);
437 _ASSERTE(strlen(name) == nameCharCount);
438}
439
440SharedMemoryId::SharedMemoryId(LPCSTR name)
441{
442 _ASSERTE(name != nullptr);
443
444 // Look for "Global\" and "Local\" prefixes in the name, and determine the session ID
445 if (strncmp(name, "Global\\", 7) == 0)
446 {
447 m_isSessionScope = false;
448 name += _countof("Global\\") - 1;
449 }
450 else
451 {
452 if (strncmp(name, "Local\\", 6) == 0)
453 {
454 name += _countof("Local\\") - 1;
455 }
456 m_isSessionScope = true;
457 }
458 m_name = name;
459
460 m_nameCharCount = strlen(name);
461 if (m_nameCharCount == 0)
462 {
463 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::NameEmpty));
464 }
465 if (m_nameCharCount > SHARED_MEMORY_MAX_FILE_NAME_CHAR_COUNT)
466 {
467 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::NameTooLong));
468 }
469
470 // Look for invalid characters '\' and '/' in the name
471 for (SIZE_T i = 0; i < m_nameCharCount; ++i)
472 {
473 char c = name[i];
474 if (c == '\\' || c == '/')
475 {
476 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::NameInvalid));
477 }
478 }
479}
480
481LPCSTR SharedMemoryId::GetName() const
482{
483 _ASSERTE(m_name != nullptr);
484 return m_name;
485}
486
487SIZE_T SharedMemoryId::GetNameCharCount() const
488{
489 _ASSERTE(m_name != nullptr);
490 return m_nameCharCount;
491}
492
493bool SharedMemoryId::IsSessionScope() const
494{
495 _ASSERTE(m_name != nullptr);
496 return m_isSessionScope;
497}
498
499bool SharedMemoryId::Equals(SharedMemoryId *other) const
500{
501 return
502 GetNameCharCount() == other->GetNameCharCount() &&
503 IsSessionScope() == other->IsSessionScope() &&
504 strcmp(GetName(), other->GetName()) == 0;
505}
506
507bool SharedMemoryId::AppendSessionDirectoryName(PathCharString& path) const
508{
509 if (IsSessionScope())
510 {
511 return path.Append(SHARED_MEMORY_SESSION_DIRECTORY_NAME_PREFIX) != FALSE
512 && SharedMemoryHelpers::AppendUInt32String(path, GetCurrentSessionId());
513 }
514 else
515 {
516 return path.Append(SHARED_MEMORY_GLOBAL_DIRECTORY_NAME) != FALSE;
517 }
518}
519
520////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
521// SharedMemorySharedDataHeader
522
523SIZE_T SharedMemorySharedDataHeader::DetermineTotalByteCount(SIZE_T dataByteCount)
524{
525 return SharedMemoryHelpers::AlignUp(sizeof(SharedMemorySharedDataHeader) + dataByteCount, GetVirtualPageSize());
526}
527
528SharedMemorySharedDataHeader::SharedMemorySharedDataHeader(SharedMemoryType type, UINT8 version)
529 : m_type(type), m_version(version)
530{
531}
532
533SharedMemoryType SharedMemorySharedDataHeader::GetType() const
534{
535 return m_type;
536}
537
538UINT8 SharedMemorySharedDataHeader::GetVersion() const
539{
540 return m_version;
541}
542
543void *SharedMemorySharedDataHeader::GetData()
544{
545 return this + 1;
546}
547
548////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
549// SharedMemoryProcessDataHeader
550
551SharedMemoryProcessDataHeader *SharedMemoryProcessDataHeader::CreateOrOpen(
552 LPCSTR name,
553 SharedMemorySharedDataHeader requiredSharedDataHeader,
554 SIZE_T sharedDataByteCount,
555 bool createIfNotExist,
556 bool *createdRef)
557{
558 _ASSERTE(name != nullptr);
559 _ASSERTE(sharedDataByteCount != 0);
560 _ASSERTE(!createIfNotExist || createdRef != nullptr);
561 _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
562 _ASSERTE(!SharedMemoryManager::IsCreationDeletionFileLockAcquired());
563
564 if (createdRef != nullptr)
565 {
566 *createdRef = false;
567 }
568
569 PathCharString filePath;
570 SharedMemoryId id(name);
571
572 struct AutoCleanup
573 {
574 bool m_acquiredCreationDeletionFileLock;
575 PathCharString *m_filePath;
576 SIZE_T m_sessionDirectoryPathCharCount;
577 bool m_createdFile;
578 int m_fileDescriptor;
579 bool m_acquiredFileLock;
580 void *m_mappedBuffer;
581 SIZE_T m_mappedBufferByteCount;
582 bool m_cancel;
583
584 AutoCleanup()
585 : m_acquiredCreationDeletionFileLock(false),
586 m_filePath(nullptr),
587 m_sessionDirectoryPathCharCount(0),
588 m_createdFile(false),
589 m_fileDescriptor(-1),
590 m_acquiredFileLock(false),
591 m_mappedBuffer(nullptr),
592 m_mappedBufferByteCount(0),
593 m_cancel(false)
594 {
595 }
596
597 ~AutoCleanup()
598 {
599 if (m_cancel)
600 {
601 return;
602 }
603
604 if (m_mappedBuffer != nullptr)
605 {
606 _ASSERTE(m_mappedBufferByteCount != 0);
607 munmap(m_mappedBuffer, m_mappedBufferByteCount);
608 }
609
610 if (m_acquiredFileLock)
611 {
612 _ASSERTE(m_fileDescriptor != -1);
613 SharedMemoryHelpers::ReleaseFileLock(m_fileDescriptor);
614 }
615
616 if (m_fileDescriptor != -1)
617 {
618 SharedMemoryHelpers::CloseFile(m_fileDescriptor);
619 }
620
621 if (m_createdFile)
622 {
623 _ASSERTE(m_filePath != nullptr);
624 unlink(*m_filePath);
625 }
626
627 if (m_sessionDirectoryPathCharCount != 0)
628 {
629 _ASSERTE(*m_filePath != nullptr);
630 m_filePath->CloseBuffer(m_sessionDirectoryPathCharCount);
631 rmdir(*m_filePath);
632 }
633
634 if (m_acquiredCreationDeletionFileLock)
635 {
636 SharedMemoryManager::ReleaseCreationDeletionFileLock();
637 }
638 }
639 } autoCleanup;
640
641 SharedMemoryProcessDataHeader *processDataHeader = SharedMemoryManager::FindProcessDataHeader(&id);
642 if (processDataHeader != nullptr)
643 {
644 _ASSERTE(
645 processDataHeader->GetSharedDataTotalByteCount() ==
646 SharedMemorySharedDataHeader::DetermineTotalByteCount(sharedDataByteCount));
647 processDataHeader->IncRefCount();
648 return processDataHeader;
649 }
650
651 SharedMemoryManager::AcquireCreationDeletionFileLock();
652 autoCleanup.m_acquiredCreationDeletionFileLock = true;
653
654 // Create the session directory
655 SharedMemoryHelpers::VerifyStringOperation(SharedMemoryManager::CopySharedMemoryBasePath(filePath));
656 SharedMemoryHelpers::VerifyStringOperation(filePath.Append('/'));
657 SharedMemoryHelpers::VerifyStringOperation(id.AppendSessionDirectoryName(filePath));
658 if (!SharedMemoryHelpers::EnsureDirectoryExists(filePath, true /* isGlobalLockAcquired */, createIfNotExist))
659 {
660 _ASSERTE(!createIfNotExist);
661 return nullptr;
662 }
663 autoCleanup.m_filePath = &filePath;
664 autoCleanup.m_sessionDirectoryPathCharCount = filePath.GetCount();
665
666 // Create or open the shared memory file
667 SharedMemoryHelpers::VerifyStringOperation(filePath.Append('/'));
668 SharedMemoryHelpers::VerifyStringOperation(filePath.Append(id.GetName(), id.GetNameCharCount()));
669
670 bool createdFile;
671 int fileDescriptor = SharedMemoryHelpers::CreateOrOpenFile(filePath, createIfNotExist, &createdFile);
672 if (fileDescriptor == -1)
673 {
674 _ASSERTE(!createIfNotExist);
675 return nullptr;
676 }
677 autoCleanup.m_createdFile = createdFile;
678 autoCleanup.m_fileDescriptor = fileDescriptor;
679
680 bool clearContents = false;
681 if (!createdFile)
682 {
683 // A shared file lock on the shared memory file would be held by any process that has opened the same file. Try to take
684 // an exclusive lock on the file. Successfully acquiring an exclusive lock indicates that no process has a reference to
685 // the shared memory file, and this process can reinitialize its contents.
686 if (SharedMemoryHelpers::TryAcquireFileLock(fileDescriptor, LOCK_EX | LOCK_NB))
687 {
688 // The shared memory file is not being used, flag it as created so that its contents will be reinitialized
689 SharedMemoryHelpers::ReleaseFileLock(fileDescriptor);
690 autoCleanup.m_createdFile = true;
691 if (!createIfNotExist)
692 {
693 return nullptr;
694 }
695 createdFile = true;
696 clearContents = true;
697 }
698 }
699
700 // Set or validate the file length
701 SIZE_T sharedDataTotalByteCount = SharedMemorySharedDataHeader::DetermineTotalByteCount(sharedDataByteCount);
702 if (createdFile)
703 {
704 SharedMemoryHelpers::SetFileSize(fileDescriptor, sharedDataTotalByteCount);
705 }
706 else if (SharedMemoryHelpers::GetFileSize(fileDescriptor) != sharedDataTotalByteCount)
707 {
708 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::HeaderMismatch));
709 }
710
711 // Acquire and hold a shared file lock on the shared memory file as long as it is open, to indicate that this process is
712 // using the file. An exclusive file lock is attempted above to detect whether the file contents are valid, for the case
713 // where a process crashes or is killed after the file is created. Since we already hold the creation/deletion locks, a
714 // non-blocking file lock should succeed.
715 if (!SharedMemoryHelpers::TryAcquireFileLock(fileDescriptor, LOCK_SH | LOCK_NB))
716 {
717 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
718 }
719 autoCleanup.m_acquiredFileLock = true;
720
721 // Map the file into memory, and initialize or validate the header
722 void *mappedBuffer = SharedMemoryHelpers::MemoryMapFile(fileDescriptor, sharedDataTotalByteCount);
723 autoCleanup.m_mappedBuffer = mappedBuffer;
724 autoCleanup.m_mappedBufferByteCount = sharedDataTotalByteCount;
725 SharedMemorySharedDataHeader *sharedDataHeader;
726 if (createdFile)
727 {
728 if (clearContents)
729 {
730 memset(mappedBuffer, 0, sharedDataTotalByteCount);
731 }
732 sharedDataHeader = new(mappedBuffer) SharedMemorySharedDataHeader(requiredSharedDataHeader);
733 }
734 else
735 {
736 sharedDataHeader = reinterpret_cast<SharedMemorySharedDataHeader *>(mappedBuffer);
737 if (sharedDataHeader->GetType() != requiredSharedDataHeader.GetType() ||
738 sharedDataHeader->GetVersion() != requiredSharedDataHeader.GetVersion())
739 {
740 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::HeaderMismatch));
741 }
742 }
743
744 // When *createdRef is true, the creation/deletion file lock will remain locked upon returning for the caller to initialize
745 // the shared data. The caller must release the file lock afterwards.
746 if (!createdFile)
747 {
748 autoCleanup.m_acquiredCreationDeletionFileLock = false;
749 SharedMemoryManager::ReleaseCreationDeletionFileLock();
750 }
751
752 processDataHeader = SharedMemoryProcessDataHeader::New(&id, fileDescriptor, sharedDataHeader, sharedDataTotalByteCount);
753
754 autoCleanup.m_cancel = true;
755 if (createdFile)
756 {
757 _ASSERTE(createIfNotExist);
758 _ASSERTE(createdRef != nullptr);
759 *createdRef = true;
760 }
761 return processDataHeader;
762}
763
764SharedMemoryProcessDataHeader *SharedMemoryProcessDataHeader::PalObject_GetProcessDataHeader(CorUnix::IPalObject *object)
765{
766 _ASSERTE(object != nullptr);
767 _ASSERTE(object->GetObjectType()->GetId() == otiNamedMutex);
768 _ASSERTE(object->GetObjectType()->GetImmutableDataSize() == sizeof(SharedMemoryProcessDataHeader *));
769
770 void *immutableDataBuffer;
771 PAL_ERROR errorCode = object->GetImmutableData(&immutableDataBuffer);
772 _ASSERTE(errorCode == NO_ERROR);
773 _ASSERTE(immutableDataBuffer != nullptr);
774 return *reinterpret_cast<SharedMemoryProcessDataHeader **>(immutableDataBuffer);
775}
776
777void SharedMemoryProcessDataHeader::PalObject_SetProcessDataHeader(
778 CorUnix::IPalObject *object,
779 SharedMemoryProcessDataHeader *processDataHeader)
780{
781 _ASSERTE(object != nullptr);
782 _ASSERTE(object->GetObjectType()->GetId() == otiNamedMutex);
783 _ASSERTE(object->GetObjectType()->GetImmutableDataSize() == sizeof(SharedMemoryProcessDataHeader *));
784 _ASSERTE(processDataHeader != nullptr);
785
786 void *immutableDataBuffer;
787 PAL_ERROR errorCode = object->GetImmutableData(&immutableDataBuffer);
788 _ASSERTE(errorCode == NO_ERROR);
789 _ASSERTE(immutableDataBuffer != nullptr);
790 *reinterpret_cast<SharedMemoryProcessDataHeader **>(immutableDataBuffer) = processDataHeader;
791}
792
793void SharedMemoryProcessDataHeader::PalObject_Close(
794 CPalThread *thread,
795 IPalObject *object,
796 bool isShuttingDown,
797 bool cleanUpPalSharedState)
798{
799 // This function's signature matches OBJECTCLEANUPROUTINE
800 _ASSERTE(thread != nullptr);
801 _ASSERTE(object != nullptr);
802 _ASSERTE(object->GetObjectType()->GetId() == otiNamedMutex);
803 _ASSERTE(object->GetObjectType()->GetImmutableDataSize() == sizeof(SharedMemoryProcessDataHeader *));
804
805 SharedMemoryProcessDataHeader *processDataHeader = PalObject_GetProcessDataHeader(object);
806 if (processDataHeader == nullptr)
807 {
808 // The object was created, but an error must have occurred before the process data was initialized
809 return;
810 }
811
812 SharedMemoryManager::AcquireCreationDeletionProcessLock();
813 processDataHeader->DecRefCount();
814 SharedMemoryManager::ReleaseCreationDeletionProcessLock();
815}
816
817SharedMemoryProcessDataHeader::SharedMemoryProcessDataHeader(
818 SharedMemoryId *id,
819 int fileDescriptor,
820 SharedMemorySharedDataHeader *sharedDataHeader,
821 SIZE_T sharedDataTotalByteCount)
822 :
823 m_refCount(1),
824 m_data(nullptr),
825 m_fileDescriptor(fileDescriptor),
826 m_sharedDataHeader(sharedDataHeader),
827 m_sharedDataTotalByteCount(sharedDataTotalByteCount),
828 m_nextInProcessDataHeaderList(nullptr)
829{
830 _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
831 _ASSERTE(id != nullptr);
832 _ASSERTE(fileDescriptor != -1);
833 _ASSERTE(sharedDataHeader != nullptr);
834 _ASSERTE(sharedDataTotalByteCount > sizeof(SharedMemorySharedDataHeader));
835 _ASSERTE(SharedMemoryHelpers::AlignDown(sharedDataTotalByteCount, GetVirtualPageSize()) == sharedDataTotalByteCount);
836
837 // Copy the name and initialize the ID
838 char *nameCopy = reinterpret_cast<char *>(this + 1);
839 SIZE_T nameByteCount = id->GetNameCharCount() + 1;
840 memcpy_s(nameCopy, nameByteCount, id->GetName(), nameByteCount);
841 m_id = SharedMemoryId(nameCopy, id->GetNameCharCount(), id->IsSessionScope());
842
843 SharedMemoryManager::AddProcessDataHeader(this);
844}
845
846SharedMemoryProcessDataHeader *SharedMemoryProcessDataHeader::New(
847 SharedMemoryId *id,
848 int fileDescriptor,
849 SharedMemorySharedDataHeader *sharedDataHeader,
850 SIZE_T sharedDataTotalByteCount)
851{
852 _ASSERTE(id != nullptr);
853
854 // Allocate space for the header and a copy of the name
855 SIZE_T nameByteCount = id->GetNameCharCount() + 1;
856 SIZE_T totalByteCount = sizeof(SharedMemoryProcessDataHeader) + nameByteCount;
857 void *buffer = SharedMemoryHelpers::Alloc(totalByteCount);
858 AutoFreeBuffer autoFreeBuffer(buffer);
859 SharedMemoryProcessDataHeader *processDataHeader =
860 new(buffer) SharedMemoryProcessDataHeader(id, fileDescriptor, sharedDataHeader, sharedDataTotalByteCount);
861 autoFreeBuffer.Cancel();
862 return processDataHeader;
863}
864
865SharedMemoryProcessDataHeader::~SharedMemoryProcessDataHeader()
866{
867 _ASSERTE(m_refCount == 0);
868 Close();
869}
870
871void SharedMemoryProcessDataHeader::Close()
872{
873 _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
874 _ASSERTE(!SharedMemoryManager::IsCreationDeletionFileLockAcquired());
875
876 // If the ref count is nonzero, we are shutting down the process abruptly without having closed some shared memory objects.
877 // There could still be threads running with active references to the shared memory object. So when the ref count is
878 // nonzero, don't clean up any object or global process-local state.
879 if (m_refCount == 0)
880 {
881 SharedMemoryManager::RemoveProcessDataHeader(this);
882 }
883
884 struct AutoReleaseCreationDeletionFileLock
885 {
886 bool m_acquired;
887
888 AutoReleaseCreationDeletionFileLock() : m_acquired(false)
889 {
890 }
891
892 ~AutoReleaseCreationDeletionFileLock()
893 {
894 if (m_acquired)
895 {
896 SharedMemoryManager::ReleaseCreationDeletionFileLock();
897 }
898 }
899 } autoReleaseCreationDeletionFileLock;
900
901 // A shared file lock on the shared memory file would be held by any process that has opened the same file. Try to take
902 // an exclusive lock on the file. Successfully acquiring an exclusive lock indicates that no process has a reference to
903 // the shared memory file, and this process can delete the file. File locks on the shared memory file are only ever acquired
904 // or released while holding the creation/deletion locks, so holding the creation/deletion locks while trying an exclusive
905 // lock on the shared memory file guarantees that another process cannot start using the shared memory file after this
906 // process has decided to delete the file.
907 bool releaseSharedData = false;
908 try
909 {
910 SharedMemoryManager::AcquireCreationDeletionFileLock();
911 autoReleaseCreationDeletionFileLock.m_acquired = true;
912
913 SharedMemoryHelpers::ReleaseFileLock(m_fileDescriptor);
914 if (SharedMemoryHelpers::TryAcquireFileLock(m_fileDescriptor, LOCK_EX | LOCK_NB))
915 {
916 SharedMemoryHelpers::ReleaseFileLock(m_fileDescriptor);
917 releaseSharedData = true;
918 }
919 }
920 catch (SharedMemoryException)
921 {
922 // Ignore the error, just don't release shared data
923 }
924
925 if (m_data != nullptr)
926 {
927 m_data->Close(m_refCount != 0 /* isAbruptShutdown */, releaseSharedData);
928 }
929
930 if (m_refCount == 0)
931 {
932 if (m_data != nullptr)
933 {
934 InternalDelete(m_data);
935 }
936
937 if (releaseSharedData)
938 {
939 m_sharedDataHeader->~SharedMemorySharedDataHeader();
940 }
941
942 munmap(m_sharedDataHeader, m_sharedDataTotalByteCount);
943 SharedMemoryHelpers::CloseFile(m_fileDescriptor);
944 }
945
946 if (!releaseSharedData)
947 {
948 return;
949 }
950
951 try
952 {
953 // Delete the shared memory file, and the session directory if it's not empty
954 PathCharString path;
955 SharedMemoryHelpers::VerifyStringOperation(SharedMemoryManager::CopySharedMemoryBasePath(path));
956 SharedMemoryHelpers::VerifyStringOperation(path.Append('/'));
957 SharedMemoryHelpers::VerifyStringOperation(m_id.AppendSessionDirectoryName(path));
958 SharedMemoryHelpers::VerifyStringOperation(path.Append('/'));
959
960 SIZE_T sessionDirectoryPathCharCount = path.GetCount();
961 SharedMemoryHelpers::VerifyStringOperation(path.Append(m_id.GetName(), m_id.GetNameCharCount()));
962 unlink(path);
963 path.CloseBuffer(sessionDirectoryPathCharCount);
964 rmdir(path);
965 }
966 catch (SharedMemoryException)
967 {
968 // Ignore the error, just don't release shared data
969 }
970}
971
972SharedMemoryId *SharedMemoryProcessDataHeader::GetId()
973{
974 return &m_id;
975}
976
977SharedMemoryProcessDataBase *SharedMemoryProcessDataHeader::GetData() const
978{
979 return m_data;
980}
981
982void SharedMemoryProcessDataHeader::SetData(SharedMemoryProcessDataBase *data)
983{
984 _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
985 _ASSERTE(m_data == nullptr);
986 _ASSERTE(data != nullptr);
987
988 m_data = data;
989}
990
991SharedMemorySharedDataHeader *SharedMemoryProcessDataHeader::GetSharedDataHeader() const
992{
993 return m_sharedDataHeader;
994}
995
996SIZE_T SharedMemoryProcessDataHeader::GetSharedDataTotalByteCount() const
997{
998 return m_sharedDataTotalByteCount;
999}
1000
1001SharedMemoryProcessDataHeader *SharedMemoryProcessDataHeader::GetNextInProcessDataHeaderList() const
1002{
1003 _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
1004 return m_nextInProcessDataHeaderList;
1005}
1006
1007void SharedMemoryProcessDataHeader::SetNextInProcessDataHeaderList(SharedMemoryProcessDataHeader *next)
1008{
1009 _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
1010 m_nextInProcessDataHeaderList = next;
1011}
1012
1013void SharedMemoryProcessDataHeader::IncRefCount()
1014{
1015 _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
1016 _ASSERTE(m_refCount != 0);
1017
1018 ++m_refCount;
1019}
1020
1021void SharedMemoryProcessDataHeader::DecRefCount()
1022{
1023 _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
1024 _ASSERTE(m_refCount != 0);
1025
1026 if (--m_refCount == 0)
1027 {
1028 InternalDelete(this);
1029 }
1030}
1031
1032////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1033// SharedMemoryManager
1034
1035CRITICAL_SECTION SharedMemoryManager::s_creationDeletionProcessLock;
1036int SharedMemoryManager::s_creationDeletionLockFileDescriptor = -1;
1037
1038SharedMemoryProcessDataHeader *SharedMemoryManager::s_processDataHeaderListHead = nullptr;
1039PathCharString* SharedMemoryManager::s_runtimeTempDirectoryPath;
1040PathCharString* SharedMemoryManager::s_sharedMemoryDirectoryPath;
1041
1042#ifdef _DEBUG
1043SIZE_T SharedMemoryManager::s_creationDeletionProcessLockOwnerThreadId = SharedMemoryHelpers::InvalidThreadId;
1044SIZE_T SharedMemoryManager::s_creationDeletionFileLockOwnerThreadId = SharedMemoryHelpers::InvalidThreadId;
1045#endif // _DEBUG
1046
1047bool SharedMemoryManager::StaticInitialize()
1048{
1049 InitializeCriticalSection(&s_creationDeletionProcessLock);
1050
1051 s_runtimeTempDirectoryPath = InternalNew<PathCharString>();
1052 s_sharedMemoryDirectoryPath = InternalNew<PathCharString>();
1053
1054 if (s_runtimeTempDirectoryPath && s_sharedMemoryDirectoryPath)
1055 {
1056 try
1057 {
1058 SharedMemoryHelpers::BuildSharedFilesPath(*s_runtimeTempDirectoryPath, SHARED_MEMORY_RUNTIME_TEMP_DIRECTORY_NAME);
1059 SharedMemoryHelpers::BuildSharedFilesPath(*s_sharedMemoryDirectoryPath, SHARED_MEMORY_SHARED_MEMORY_DIRECTORY_NAME);
1060
1061 return true;
1062 }
1063 catch (SharedMemoryException)
1064 {
1065 // Ignore and let function fail
1066 }
1067 }
1068 return false;
1069}
1070
1071void SharedMemoryManager::StaticClose()
1072{
1073 // This function could very well be running during abrupt shutdown, and there could still be user threads running.
1074 // Synchronize the deletion, and don't remove or delete items in the linked list.
1075 AcquireCreationDeletionProcessLock();
1076 for (SharedMemoryProcessDataHeader *current = s_processDataHeaderListHead;
1077 current != nullptr;
1078 current = current->GetNextInProcessDataHeaderList())
1079 {
1080 current->Close();
1081 }
1082 ReleaseCreationDeletionProcessLock();
1083
1084 // This function could very well be running during abrupt shutdown, and there could still be user threads running. Don't
1085 // delete the creation/deletion process lock, the process is shutting down anyway.
1086}
1087
1088void SharedMemoryManager::AcquireCreationDeletionProcessLock()
1089{
1090 _ASSERTE(!IsCreationDeletionProcessLockAcquired());
1091 _ASSERTE(!IsCreationDeletionFileLockAcquired());
1092
1093 EnterCriticalSection(&s_creationDeletionProcessLock);
1094#ifdef _DEBUG
1095 s_creationDeletionProcessLockOwnerThreadId = THREADSilentGetCurrentThreadId();
1096#endif // _DEBUG
1097}
1098
1099void SharedMemoryManager::ReleaseCreationDeletionProcessLock()
1100{
1101 _ASSERTE(IsCreationDeletionProcessLockAcquired());
1102 _ASSERTE(!IsCreationDeletionFileLockAcquired());
1103
1104#ifdef _DEBUG
1105 s_creationDeletionProcessLockOwnerThreadId = SharedMemoryHelpers::InvalidThreadId;
1106#endif // _DEBUG
1107 LeaveCriticalSection(&s_creationDeletionProcessLock);
1108}
1109
1110void SharedMemoryManager::AcquireCreationDeletionFileLock()
1111{
1112 _ASSERTE(IsCreationDeletionProcessLockAcquired());
1113 _ASSERTE(!IsCreationDeletionFileLockAcquired());
1114
1115 if (s_creationDeletionLockFileDescriptor == -1)
1116 {
1117 if (!SharedMemoryHelpers::EnsureDirectoryExists(
1118 *gSharedFilesPath,
1119 false /* isGlobalLockAcquired */,
1120 false /* createIfNotExist */,
1121 true /* isSystemDirectory */))
1122 {
1123 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
1124 }
1125 SharedMemoryHelpers::EnsureDirectoryExists(
1126 *s_runtimeTempDirectoryPath,
1127 false /* isGlobalLockAcquired */);
1128 SharedMemoryHelpers::EnsureDirectoryExists(
1129 *s_sharedMemoryDirectoryPath,
1130 false /* isGlobalLockAcquired */);
1131 s_creationDeletionLockFileDescriptor = SharedMemoryHelpers::OpenDirectory(*s_sharedMemoryDirectoryPath);
1132 if (s_creationDeletionLockFileDescriptor == -1)
1133 {
1134 throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
1135 }
1136 }
1137
1138 bool acquiredFileLock = SharedMemoryHelpers::TryAcquireFileLock(s_creationDeletionLockFileDescriptor, LOCK_EX);
1139 _ASSERTE(acquiredFileLock);
1140#ifdef _DEBUG
1141 s_creationDeletionFileLockOwnerThreadId = THREADSilentGetCurrentThreadId();
1142#endif // _DEBUG
1143}
1144
1145void SharedMemoryManager::ReleaseCreationDeletionFileLock()
1146{
1147 _ASSERTE(IsCreationDeletionProcessLockAcquired());
1148 _ASSERTE(IsCreationDeletionFileLockAcquired());
1149 _ASSERTE(s_creationDeletionLockFileDescriptor != -1);
1150
1151#ifdef _DEBUG
1152 s_creationDeletionFileLockOwnerThreadId = SharedMemoryHelpers::InvalidThreadId;
1153#endif // _DEBUG
1154 SharedMemoryHelpers::ReleaseFileLock(s_creationDeletionLockFileDescriptor);
1155}
1156
1157#ifdef _DEBUG
1158bool SharedMemoryManager::IsCreationDeletionProcessLockAcquired()
1159{
1160 return s_creationDeletionProcessLockOwnerThreadId == THREADSilentGetCurrentThreadId();
1161}
1162
1163bool SharedMemoryManager::IsCreationDeletionFileLockAcquired()
1164{
1165 return s_creationDeletionFileLockOwnerThreadId == THREADSilentGetCurrentThreadId();
1166}
1167#endif // _DEBUG
1168
1169void SharedMemoryManager::AddProcessDataHeader(SharedMemoryProcessDataHeader *processDataHeader)
1170{
1171 _ASSERTE(processDataHeader != nullptr);
1172 _ASSERTE(IsCreationDeletionProcessLockAcquired());
1173 _ASSERTE(processDataHeader->GetNextInProcessDataHeaderList() == nullptr);
1174 _ASSERTE(FindProcessDataHeader(processDataHeader->GetId()) == nullptr);
1175
1176 processDataHeader->SetNextInProcessDataHeaderList(s_processDataHeaderListHead);
1177 s_processDataHeaderListHead = processDataHeader;
1178}
1179
1180void SharedMemoryManager::RemoveProcessDataHeader(SharedMemoryProcessDataHeader *processDataHeader)
1181{
1182 _ASSERTE(processDataHeader != nullptr);
1183 _ASSERTE(IsCreationDeletionProcessLockAcquired());
1184
1185 if (s_processDataHeaderListHead == processDataHeader)
1186 {
1187 s_processDataHeaderListHead = processDataHeader->GetNextInProcessDataHeaderList();
1188 processDataHeader->SetNextInProcessDataHeaderList(nullptr);
1189 return;
1190 }
1191 for (SharedMemoryProcessDataHeader
1192 *previous = s_processDataHeaderListHead,
1193 *current = previous->GetNextInProcessDataHeaderList();
1194 current != nullptr;
1195 previous = current, current = current->GetNextInProcessDataHeaderList())
1196 {
1197 if (current == processDataHeader)
1198 {
1199 previous->SetNextInProcessDataHeaderList(current->GetNextInProcessDataHeaderList());
1200 current->SetNextInProcessDataHeaderList(nullptr);
1201 return;
1202 }
1203 }
1204 _ASSERTE(false);
1205}
1206
1207SharedMemoryProcessDataHeader *SharedMemoryManager::FindProcessDataHeader(SharedMemoryId *id)
1208{
1209 _ASSERTE(IsCreationDeletionProcessLockAcquired());
1210
1211 // TODO: Use a hash table
1212 for (SharedMemoryProcessDataHeader *current = s_processDataHeaderListHead;
1213 current != nullptr;
1214 current = current->GetNextInProcessDataHeaderList())
1215 {
1216 if (current->GetId()->Equals(id))
1217 {
1218 return current;
1219 }
1220 }
1221 return nullptr;
1222}
1223
1224bool SharedMemoryManager::CopySharedMemoryBasePath(PathCharString& destination)
1225{
1226 return destination.Set(*s_sharedMemoryDirectoryPath) != FALSE;
1227}
1228