File: src\libraries\System.Private.CoreLib\src\System\IO\SharedMemoryManager.Unix.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using System.Threading;
using Microsoft.Win32.SafeHandles;
 
namespace System.IO
{
    internal readonly unsafe struct SharedMemoryId
    {
        private const string UserUnscopedRuntimeTempDirectoryName = ".dotnet";
 
        private const string UserScopedRuntimeTempDirectoryName = ".dotnet-uid";
 
        private const string SharedMemoryGlobalDirectoryName = "global";
 
        private const string SharedMemorySessionDirectoryName = "session";
 
        private static int SessionId { get; } = Interop.Sys.GetSid(Environment.ProcessId);
 
        public SharedMemoryId(string name, bool isUserScope)
        {
            if (name.StartsWith("Global\\", StringComparison.Ordinal))
            {
                IsSessionScope = false;
                name = name.Substring("Global\\".Length);
            }
            else
            {
                IsSessionScope = true;
                if (name.StartsWith("Local\\", StringComparison.Ordinal))
                {
                    name = name.Substring("Local\\".Length);
                }
            }
 
            Name = name;
 
            if (name.Contains(Path.DirectorySeparatorChar))
            {
                throw new IOException(SR.Argument_DirectorySeparatorInvalid);
            }
 
            IsUserScope = isUserScope;
            Uid = IsUserScope ? Interop.Sys.GetEUid() : 0;
        }
 
        public string Name { get; }
        public bool IsSessionScope { get; }
        public bool IsUserScope { get; }
        public uint Uid { get; }
 
        internal readonly string GetRuntimeTempDirectoryName()
        {
            if (IsUserScope)
            {
                return $"{UserScopedRuntimeTempDirectoryName}{Uid}";
            }
            else
            {
                return UserUnscopedRuntimeTempDirectoryName;
            }
        }
 
        internal readonly string GetSessionDirectoryName()
        {
            if (IsSessionScope)
            {
                return $"{SharedMemorySessionDirectoryName}{SessionId}";
            }
            else
            {
                return SharedMemoryGlobalDirectoryName;
            }
        }
    }
 
    internal enum SharedMemoryType : byte
    {
        Mutex
    }
 
    [StructLayout(LayoutKind.Explicit)]
    internal struct SharedMemorySharedDataHeader
    {
        private struct SharedMemoryAndVersion
        {
            public SharedMemoryType Type;
            public byte Version;
        }
 
        [FieldOffset(0)]
        private SharedMemoryAndVersion _data;
 
        [FieldOffset(0)]
        private ulong _raw;
 
        public readonly SharedMemoryType Type => _data.Type;
        public readonly byte Version => _data.Version;
 
        public SharedMemorySharedDataHeader(SharedMemoryType type, byte version)
        {
            _data = new SharedMemoryAndVersion
            {
                Type = type,
                Version = version
            };
        }
    }
 
    internal interface ISharedMemoryProcessData
    {
        void Close(bool releaseSharedData);
    }
 
    internal sealed unsafe class SharedMemoryProcessDataHeader<TSharedMemoryProcessData>
        where TSharedMemoryProcessData : class, ISharedMemoryProcessData
    {
        internal readonly SharedMemoryId _id;
        internal TSharedMemoryProcessData? _processData;
        private readonly SafeFileHandle _fileHandle;
        private readonly SharedMemorySharedDataHeader* _sharedDataHeader;
        private readonly nuint _sharedDataTotalByteCount;
        private int _referenceCount = 1;
 
        public SharedMemoryProcessDataHeader(SharedMemoryId id, SafeFileHandle fileHandle, SharedMemorySharedDataHeader* sharedDataHeader, nuint sharedDataTotalByteCount)
        {
            _id = id;
            _fileHandle = fileHandle;
            _sharedDataHeader = sharedDataHeader;
            _sharedDataTotalByteCount = sharedDataTotalByteCount;
            _processData = null; // Will be initialized later
            SharedMemoryManager<TSharedMemoryProcessData>.Instance.AddProcessDataHeader(this);
        }
 
        public static void* GetDataPointer(SharedMemoryProcessDataHeader<TSharedMemoryProcessData>? processDataHeader)
        {
            return processDataHeader is null
                ? null
                : (void*)((byte*)processDataHeader._sharedDataHeader + sizeof(SharedMemorySharedDataHeader));
        }
 
        internal static SharedMemoryProcessDataHeader<TSharedMemoryProcessData>? CreateOrOpen(
            string name,
            bool isUserScope,
            SharedMemorySharedDataHeader requiredSharedDataHeader,
            nuint sharedMemoryDataSize,
            bool createIfNotExist,
            bool acquireLockIfCreated,
            out bool created,
            out AutoReleaseFileLock creationDeletionLockFileHandle)
        {
            created = false;
 
            // If we don't create the shared memory file, the caller won't need to hold the creation/deletion lock file handle.
            // Return a placeholder to simplify the caller's logic and allow them to always dispose of the handle
            // when the return value is non-null.
            creationDeletionLockFileHandle = new AutoReleaseFileLock(new SafeFileHandle());
            SharedMemoryId id = new(name, isUserScope);
 
            nuint sharedDataUsedByteCount = (nuint)sizeof(SharedMemorySharedDataHeader) + sharedMemoryDataSize;
            nuint sharedDataTotalByteCount = AlignUp(sharedDataUsedByteCount, (nuint)Environment.SystemPageSize);
 
            SharedMemoryProcessDataHeader<TSharedMemoryProcessData>? processDataHeader = SharedMemoryManager<TSharedMemoryProcessData>.Instance.FindProcessDataHeader(id);
 
            if (processDataHeader is not null)
            {
                Debug.Assert(processDataHeader._sharedDataTotalByteCount == sharedDataTotalByteCount);
                processDataHeader.IncrementRefCount();
                return processDataHeader;
            }
 
            using AutoReleaseFileLock creationDeletionLock = SharedMemoryManager<TSharedMemoryProcessData>.Instance.AcquireCreationDeletionLockForId(id);
 
            string sessionDirectory = Path.Combine(
                SharedMemoryHelpers.SharedFilesPath,
                id.GetRuntimeTempDirectoryName(),
                SharedMemoryManager<TSharedMemoryProcessData>.SharedMemorySharedMemoryDirectoryName,
                id.GetSessionDirectoryName()
            );
 
            if (!SharedMemoryHelpers.EnsureDirectoryExists(sessionDirectory, id, isGlobalLockAcquired: true, createIfNotExist))
            {
                Debug.Assert(!createIfNotExist);
                return null;
            }
 
            string sharedMemoryFilePath = Path.Combine(sessionDirectory, id.Name);
 
            SafeFileHandle fileHandle = SharedMemoryHelpers.CreateOrOpenFile(sharedMemoryFilePath, id, createIfNotExist, out bool createdFile);
            if (fileHandle.IsInvalid)
            {
                return null;
            }
 
            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(fileHandle, nonBlocking: true))
                {
                    // The shared memory file is not being used, flag it as created so that its contents will be reinitialized
                    Interop.Sys.FLock(fileHandle, Interop.Sys.LockOperations.LOCK_UN);
                    if (!createIfNotExist)
                    {
                        return null;
                    }
                    createdFile = true;
                    clearContents = true;
                }
            }
 
            if (createdFile)
            {
                if (Interop.Sys.FTruncate(fileHandle, (long)sharedDataTotalByteCount) < 0)
                {
                    Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo();
                    throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath);
                }
            }
            else
            {
                if (Interop.Sys.FStat(fileHandle, out Interop.Sys.FileStatus fileStatus) != 0)
                {
                    Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo();
                    throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath);
                }
 
                if (fileStatus.Size < (long)sharedDataUsedByteCount)
                {
                    throw new InvalidDataException(SR.Format(SR.IO_SharedMemory_InvalidHeader, sharedMemoryFilePath));
                }
 
                if (Interop.Sys.FTruncate(fileHandle, (long)sharedDataTotalByteCount) < 0)
                {
                    Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo();
                    throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath);
                }
            }
 
            // 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(fileHandle, nonBlocking: true, exclusive: false))
            {
                Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
                throw Interop.GetExceptionForIoErrno(errorInfo, sharedMemoryFilePath);
            }
 
            using AutoReleaseFileLock autoReleaseFileLock = new(fileHandle);
 
            using MemoryMappedFileHolder memory = SharedMemoryHelpers.MemoryMapFile(fileHandle, sharedDataTotalByteCount);
 
            SharedMemorySharedDataHeader* sharedDataHeader = (SharedMemorySharedDataHeader*)memory.Pointer;
            if (createdFile)
            {
                if (clearContents)
                {
                    NativeMemory.Clear(memory.Pointer, sharedDataTotalByteCount);
                }
                *sharedDataHeader = requiredSharedDataHeader;
            }
            else
            {
                if (sharedDataHeader->Type != requiredSharedDataHeader.Type ||
                    sharedDataHeader->Version != requiredSharedDataHeader.Version)
                {
                    throw new InvalidDataException(SR.Format(SR.IO_SharedMemory_InvalidHeader, sharedMemoryFilePath));
                }
            }
 
            if (createdFile)
            {
                // If we created the file, then the caller still has more work to do to initialize the shared memory data.
                // Transfer the creation/deletion lock file handle to the caller to hold while they do that work.
                creationDeletionLock.SuppressRelease();
                creationDeletionLockFileHandle = new AutoReleaseFileLock(creationDeletionLock.FileHandle);
            }
 
            processDataHeader = new SharedMemoryProcessDataHeader<TSharedMemoryProcessData>(
                id,
                fileHandle,
                sharedDataHeader,
                sharedDataTotalByteCount
            );
 
            autoReleaseFileLock.SuppressRelease();
            memory.SuppressRelease();
 
            if (createdFile)
            {
                created = true;
            }
 
            return processDataHeader;
 
            static nuint AlignUp(nuint value, nuint alignment)
            {
                nuint alignMask = alignment - 1;
                return (nuint)((value + alignMask) & ~alignMask);
            }
        }
 
        public void IncrementRefCount()
        {
            Debug.Assert(_referenceCount > 0, "Ref count should not be negative.");
            _referenceCount++;
        }
 
        public void DecrementRefCount()
        {
            Debug.Assert(_referenceCount > 0, "Ref count should not be negative.");
            _referenceCount--;
            if (_referenceCount == 0)
            {
                Close();
            }
        }
 
        private void Close()
        {
            SharedMemoryManager<TSharedMemoryProcessData>.Instance.VerifyCreationDeletionProcessLockIsLocked();
            SharedMemoryManager<TSharedMemoryProcessData>.Instance.RemoveProcessDataHeader(this);
 
            using AutoReleaseFileLock autoReleaseFileLock = SharedMemoryManager<TSharedMemoryProcessData>.Instance.AcquireCreationDeletionLockForId(_id);
 
            bool releaseSharedData = false;
 
            try
            {
                Interop.Sys.FLock(_fileHandle, Interop.Sys.LockOperations.LOCK_UN);
                if (SharedMemoryHelpers.TryAcquireFileLock(_fileHandle, nonBlocking: true, exclusive: true))
                {
                    // There's no one else using this mutex.
                    // We can delete our shared data.
                    releaseSharedData = true;
                }
            }
            catch (Exception)
            {
                // Ignore the error, just don't release shared data.
            }
 
            _processData?.Close(releaseSharedData);
            _processData = null;
            Interop.Sys.MUnmap((nint)_sharedDataHeader, _sharedDataTotalByteCount);
            _fileHandle.Dispose();
 
            if (releaseSharedData)
            {
                string sessionDirectoryPath = Path.Combine(
                    SharedMemoryHelpers.SharedFilesPath,
                    _id.GetRuntimeTempDirectoryName(),
                    SharedMemoryManager<TSharedMemoryProcessData>.SharedMemorySharedMemoryDirectoryName,
                    _id.GetSessionDirectoryName()
                );
 
                string sharedMemoryFilePath = Path.Combine(sessionDirectoryPath, _id.Name);
 
                // Directly call the underlying functions here as this is best-effort.
                // If we fail to delete, we don't want an exception.
                Interop.Sys.Unlink(sharedMemoryFilePath);
 
                Interop.Sys.RmDir(sessionDirectoryPath);
            }
        }
    }
 
    internal static class SharedMemoryHelpers
    {
        private const UnixFileMode PermissionsMask_OwnerUser_ReadWrite = UnixFileMode.UserRead | UnixFileMode.UserWrite;
        private const UnixFileMode PermissionsMask_OwnerUser_ReadWriteExecute = PermissionsMask_OwnerUser_ReadWrite | UnixFileMode.UserExecute;
        private const UnixFileMode PermissionsMask_NonOwnerUsers_Write = UnixFileMode.GroupWrite | UnixFileMode.OtherWrite;
        private const UnixFileMode PermissionsMask_AllUsers_ReadWrite = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.OtherRead | UnixFileMode.OtherWrite;
        private const UnixFileMode PermissionsMask_AllUsers_ReadWriteExecute = PermissionsMask_AllUsers_ReadWrite | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute;
        private const UnixFileMode PermissionsMask_Sticky = UnixFileMode.StickyBit;
 
        private const string SharedMemoryUniqueTempNameTemplate = ".dotnet.XXXXXX";
 
        // See https://developer.apple.com/documentation/Foundation/FileManager/containerURL(forSecurityApplicationGroupIdentifier:)#App-Groups-in-macOS for details on this path.
        private const string ApplicationContainerBasePathSuffix = "/Library/Group Containers/";
 
        public static string SharedFilesPath { get; } = InitalizeSharedFilesPath();
        private static string InitalizeSharedFilesPath()
        {
            if (OperatingSystem.IsApplePlatform())
            {
                string? applicationGroupId = Environment.GetEnvironmentVariable("DOTNET_SHARED_MEMORY_APPLICATION_GROUP_ID");
                if (applicationGroupId is not null)
                {
                    string sharedFilesPath = Path.Combine(
                        PersistedFiles.GetHomeDirectoryFromPasswd(),
                        ApplicationContainerBasePathSuffix,
                        applicationGroupId
                    );
 
                    if (File.Exists(sharedFilesPath))
                    {
                        // If the path exists and is a file, throw an exception.
                        // If it's a directory, or does not exist, callers can correctly handle it.
                        throw new DirectoryNotFoundException();
                    }
 
                    return sharedFilesPath;
                }
            }
 
            return "/tmp/";
        }
 
        internal static SafeFileHandle CreateOrOpenFile(string sharedMemoryFilePath, SharedMemoryId id, bool createIfNotExist, out bool createdFile)
        {
            SafeFileHandle fd = Interop.Sys.Open(sharedMemoryFilePath, Interop.Sys.OpenFlags.O_RDWR | Interop.Sys.OpenFlags.O_CLOEXEC, 0);
            Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo();
            if (!fd.IsInvalid)
            {
                if (id.IsUserScope)
                {
                    if (Interop.Sys.FStat(fd, out Interop.Sys.FileStatus fileStatus) != 0)
                    {
                        error = Interop.Sys.GetLastErrorInfo();
                        fd.Dispose();
                        throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath);
                    }
 
                    if (fileStatus.Uid != id.Uid)
                    {
                        fd.Dispose();
                        throw new IOException(SR.Format(SR.IO_SharedMemory_FileNotOwnedByUid, sharedMemoryFilePath, id.Uid));
                    }
 
                    if ((fileStatus.Mode & (int)PermissionsMask_AllUsers_ReadWriteExecute) != (int)PermissionsMask_OwnerUser_ReadWrite)
                    {
                        fd.Dispose();
                        throw new IOException(SR.Format(SR.IO_SharedMemory_FilePermissionsIncorrect, sharedMemoryFilePath, PermissionsMask_OwnerUser_ReadWrite));
                    }
                }
                createdFile = false;
                return fd;
            }
 
            if (error.Error != Interop.Error.ENOENT)
            {
                throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath);
            }
 
            if (!createIfNotExist)
            {
                createdFile = false;
                return fd;
            }
 
            fd.Dispose();
 
            UnixFileMode permissionsMask = id.IsUserScope
                ? PermissionsMask_OwnerUser_ReadWrite
                : PermissionsMask_AllUsers_ReadWrite;
 
            fd = Interop.Sys.Open(
                sharedMemoryFilePath,
                Interop.Sys.OpenFlags.O_RDWR | Interop.Sys.OpenFlags.O_CLOEXEC | Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_EXCL,
                (int)permissionsMask);
 
            if (fd.IsInvalid)
            {
                error = Interop.Sys.GetLastErrorInfo();
                throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath);
            }
 
            int result = Interop.Sys.FChMod(fd, (int)permissionsMask);
 
            if (result != 0)
            {
                error = Interop.Sys.GetLastErrorInfo();
                fd.Dispose();
                Interop.Sys.Unlink(sharedMemoryFilePath);
                throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath);
            }
 
            createdFile = true;
            return fd;
        }
 
        internal static bool EnsureDirectoryExists(string directoryPath, SharedMemoryId id, bool isGlobalLockAcquired, bool createIfNotExist = true, bool isSystemDirectory = false)
        {
            UnixFileMode permissionsMask = id.IsUserScope
                ? PermissionsMask_OwnerUser_ReadWriteExecute
                : PermissionsMask_AllUsers_ReadWriteExecute;
 
            int statResult = Interop.Sys.Stat(directoryPath, out Interop.Sys.FileStatus fileStatus);
 
            if (statResult != 0 && Interop.Sys.GetLastError() == Interop.Error.ENOENT)
            {
                if (!createIfNotExist)
                {
                    // The directory does not exist and we are not allowed to create it.
                    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)
                {
#pragma warning disable CA1416 // Validate platform compatibility. This file is only included on Unix platforms.
                    Directory.CreateDirectory(directoryPath, permissionsMask);
#pragma warning restore CA1416 // Validate platform compatibility
 
                    try
                    {
                        FileSystem.SetUnixFileMode(directoryPath, permissionsMask);
                    }
                    catch (Exception)
                    {
                        Directory.Delete(directoryPath);
                        throw;
                    }
 
                    return true;
                }
 
                string tempPath = Path.Combine(SharedFilesPath, SharedMemoryUniqueTempNameTemplate);
 
                unsafe
                {
                    byte* tempPathPtr = Utf8StringMarshaller.ConvertToUnmanaged(tempPath);
                    if (Interop.Sys.MkdTemp(tempPathPtr) == null)
                    {
                        Utf8StringMarshaller.Free(tempPathPtr);
                        Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo();
                        throw Interop.GetExceptionForIoErrno(error, tempPath);
                    }
                    // Convert the path back to get the substituted path.
                    tempPath = Utf8StringMarshaller.ConvertToManaged(tempPathPtr)!;
                    Utf8StringMarshaller.Free(tempPathPtr);
                }
 
                try
                {
                    FileSystem.SetUnixFileMode(tempPath, permissionsMask);
                }
                catch (Exception)
                {
                    Directory.Delete(tempPath);
                    throw;
                }
 
                if (Interop.Sys.Rename(tempPath, directoryPath) == 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.
                Directory.Delete(tempPath);
                statResult = Interop.Sys.Stat(directoryPath, out fileStatus);
            }
 
            // If the path exists, check that it's a directory
            if (statResult != 0 || (fileStatus.Mode & Interop.Sys.FileTypes.S_IFDIR) == 0)
            {
                if (statResult != 0)
                {
                    Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo();
                    if (error.Error != Interop.Error.ENOENT)
                    {
                        throw Interop.GetExceptionForIoErrno(error, directoryPath);
                    }
                }
                else
                {
                    throw new IOException(SR.Format(SR.IO_SharedMemory_PathExistsButNotDirectory, directoryPath));
                }
            }
 
            if (isSystemDirectory)
            {
                // For system directories (such as TEMP_DIRECTORY_PATH), require sufficient permissions only for the
                // owner user. For instance, "docker run --mount ..." to mount /tmp to some directory on the host mounts the
                // destination directory with the same permissions as the source directory, which may not include some permissions for
                // other users. In the docker container, other user permissions are typically not relevant and relaxing the permissions
                // requirement allows for that scenario to work without having to work around it by first giving sufficient permissions
                // for all users.
                //
                // If the directory is being used for user-scoped shared memory data, also ensure that either it has the sticky bit or
                // it's owned by the current user and without write access for other users.
 
                permissionsMask = PermissionsMask_OwnerUser_ReadWriteExecute;
                if ((fileStatus.Mode & (int)permissionsMask) == (int)permissionsMask
                    && (
                        !id.IsUserScope ||
                        (fileStatus.Mode & (int)PermissionsMask_Sticky) == (int)PermissionsMask_Sticky ||
                        (fileStatus.Uid == id.Uid && (fileStatus.Mode & (int)PermissionsMask_NonOwnerUsers_Write) == 0)
                    ))
                {
                    return true;
                }
 
                throw new IOException(SR.Format(SR.IO_SharedMemory_DirectoryPermissionsIncorrect, directoryPath, fileStatus.Uid, Convert.ToString(fileStatus.Mode, 8)));
            }
 
            // For non-system directories (such as SharedFilesPath/UserUnscopedRuntimeTempDirectoryName),
            // require the sufficient permissions and try to update them if requested to create the directory, so that
            // shared memory files may be shared according to its scope.
 
            // For user-scoped directories, verify the owner UID
            if (id.IsUserScope && fileStatus.Uid != id.Uid)
            {
                throw new IOException(SR.Format(SR.IO_SharedMemory_DirectoryNotOwnedByUid, directoryPath, id.Uid));
            }
 
            // Verify the permissions, or try to change them if possible
            if ((fileStatus.Mode & (int)PermissionsMask_AllUsers_ReadWriteExecute) == (int)permissionsMask
                || (createIfNotExist && Interop.Sys.ChMod(directoryPath, (int)permissionsMask) == 0))
            {
                return true;
            }
 
            // We were not able to verify or set the necessary permissions. For user-scoped directories, this is treated as a failure
            // since other users aren't sufficiently restricted in permissions.
            if (id.IsUserScope)
            {
                throw new IOException(SR.Format(SR.IO_SharedMemory_DirectoryPermissionsIncorrectUserScope, directoryPath, Convert.ToString(fileStatus.Mode, 8)));
            }
 
 
            // For user-unscoped directories, as a last resort, check that at least the owner user has full access.
            permissionsMask = PermissionsMask_OwnerUser_ReadWriteExecute;
            if ((fileStatus.Mode & (int)permissionsMask) != (int)permissionsMask)
            {
                throw new IOException(SR.Format(SR.IO_SharedMemory_DirectoryOwnerPermissionsIncorrect, directoryPath, Convert.ToString(fileStatus.Mode, 8)));
            }
 
            return true;
        }
 
        internal static MemoryMappedFileHolder MemoryMapFile(SafeFileHandle fileHandle, nuint sharedDataTotalByteCount)
        {
            nint addr = Interop.Sys.MMap(
                0,
                sharedDataTotalByteCount,
                Interop.Sys.MemoryMappedProtections.PROT_READ | Interop.Sys.MemoryMappedProtections.PROT_WRITE,
                Interop.Sys.MemoryMappedFlags.MAP_SHARED,
                fileHandle,
                0);
 
            if (addr == -1)
            {
                Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
                throw Interop.GetExceptionForIoErrno(errorInfo, "Failed to memory map the file");
            }
 
            return new MemoryMappedFileHolder(addr, sharedDataTotalByteCount);
        }
 
        internal static bool TryAcquireFileLock(SafeFileHandle sharedLockFileHandle, bool nonBlocking, bool exclusive = true)
        {
            Interop.Sys.LockOperations lockOperation = exclusive ? Interop.Sys.LockOperations.LOCK_EX : Interop.Sys.LockOperations.LOCK_SH;
            if (nonBlocking)
            {
                lockOperation |= Interop.Sys.LockOperations.LOCK_NB;
            }
            int result = Interop.Sys.FLock(sharedLockFileHandle, lockOperation);
 
            if (result == 0)
            {
                return true;
            }
 
            Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
            if (errorInfo.Error == Interop.Error.EWOULDBLOCK)
            {
                return false;
            }
 
            throw Interop.GetExceptionForIoErrno(errorInfo);
        }
    }
 
    internal unsafe ref struct MemoryMappedFileHolder(nint addr, nuint length)
    {
        private bool _suppressed;
 
        public void SuppressRelease()
        {
            _suppressed = true;
        }
 
        public void Dispose()
        {
            if (!_suppressed)
            {
                Interop.Sys.MUnmap(addr, length);
            }
        }
 
        public void* Pointer => (void*)addr;
    }
 
    internal unsafe ref struct AutoReleaseFileLock(SafeFileHandle fd)
    {
        private bool _suppressed;
 
        public readonly SafeFileHandle FileHandle = fd;
 
        public void SuppressRelease()
        {
            _suppressed = true;
        }
 
        public void Dispose()
        {
            if (!_suppressed && !FileHandle.IsInvalid)
            {
                Interop.Sys.FLock(FileHandle, Interop.Sys.LockOperations.LOCK_UN);
            }
        }
    }
 
    internal sealed class SharedMemoryManager<TSharedMemoryProcessData>
        where TSharedMemoryProcessData : class, ISharedMemoryProcessData
    {
        internal static SharedMemoryManager<TSharedMemoryProcessData> Instance { get; } = new SharedMemoryManager<TSharedMemoryProcessData>();
 
        internal const string SharedMemorySharedMemoryDirectoryName = "shm";
 
        private readonly LowLevelLock _creationDeletionProcessLock = new();
        private SafeFileHandle? _creationDeletionLockFileHandle;
        private readonly Dictionary<uint, SafeFileHandle> _uidToFileHandleMap = [];
 
        public WaitSubsystem.LockHolder AcquireCreationDeletionProcessLock()
        {
            return new WaitSubsystem.LockHolder(_creationDeletionProcessLock);
        }
 
        public void VerifyCreationDeletionProcessLockIsLocked()
        {
            _creationDeletionProcessLock.VerifyIsLocked();
        }
 
        public AutoReleaseFileLock AcquireCreationDeletionLockForId(SharedMemoryId id)
        {
            _creationDeletionProcessLock.VerifyIsLocked();
            SafeFileHandle? fd = id.IsUserScope ? GetUserScopeCreationDeletionLockFileHandle(id.Uid) : _creationDeletionLockFileHandle;
            if (fd is null)
            {
                if (!SharedMemoryHelpers.EnsureDirectoryExists(SharedMemoryHelpers.SharedFilesPath, id, isGlobalLockAcquired: false, createIfNotExist: false, isSystemDirectory: true))
                {
                    Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo();
                    throw Interop.GetExceptionForIoErrno(error, SharedMemoryHelpers.SharedFilesPath);
                }
                string runtimeTempDirectory = Path.Combine(
                    SharedMemoryHelpers.SharedFilesPath,
                    id.GetRuntimeTempDirectoryName());
 
                SharedMemoryHelpers.EnsureDirectoryExists(runtimeTempDirectory, id, isGlobalLockAcquired: false);
 
                string sharedMemoryDirectory = Path.Combine(
                    runtimeTempDirectory,
                    SharedMemorySharedMemoryDirectoryName);
 
                SharedMemoryHelpers.EnsureDirectoryExists(sharedMemoryDirectory, id, isGlobalLockAcquired: false);
 
                fd = Interop.Sys.Open(sharedMemoryDirectory, Interop.Sys.OpenFlags.O_RDONLY, 0);
                if (fd.IsInvalid)
                {
                    Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo();
                    fd.Dispose();
                    throw Interop.GetExceptionForIoErrno(error, sharedMemoryDirectory);
                }
 
                if (id.IsUserScope)
                {
                    _uidToFileHandleMap.Add(id.Uid, fd);
                }
                else
                {
                    _creationDeletionLockFileHandle = fd;
                }
            }
 
            bool acquired = SharedMemoryHelpers.TryAcquireFileLock(fd, nonBlocking: false, exclusive: true);
            Debug.Assert(acquired);
            return new AutoReleaseFileLock(fd);
 
            SafeFileHandle? GetUserScopeCreationDeletionLockFileHandle(uint uid)
            {
                _uidToFileHandleMap.TryGetValue(uid, out SafeFileHandle? fileHandle);
                return fileHandle;
            }
        }
 
        private Dictionary<SharedMemoryId, SharedMemoryProcessDataHeader<TSharedMemoryProcessData>> _processDataHeaders = [];
 
        public void AddProcessDataHeader(SharedMemoryProcessDataHeader<TSharedMemoryProcessData> processDataHeader)
        {
            VerifyCreationDeletionProcessLockIsLocked();
            _processDataHeaders[processDataHeader._id] = processDataHeader;
        }
 
        public void RemoveProcessDataHeader(SharedMemoryProcessDataHeader<TSharedMemoryProcessData> processDataHeader)
        {
            VerifyCreationDeletionProcessLockIsLocked();
            _processDataHeaders.Remove(processDataHeader._id);
        }
 
        public SharedMemoryProcessDataHeader<TSharedMemoryProcessData>? FindProcessDataHeader(SharedMemoryId id)
        {
            _processDataHeaders.TryGetValue(id, out SharedMemoryProcessDataHeader<TSharedMemoryProcessData>? header);
            return header;
        }
    }
}