File: src\runtime\src\libraries\System.Private.CoreLib\src\System\IO\SharedMemoryManager.Unix.cs
Web Access
Project: 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.Diagnostics.CodeAnalysis;
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
            {
                // 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 int TransientRetryCount = 10;

        // 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)
        {
            int transientRetryCount = 0;

            while (true)
            {
                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();

                            // Another process may have created this file and not yet applied the final mode via fchmod.
                            // Retry briefly in case this open races with that transient window.
                            if (transientRetryCount < TransientRetryCount)
                            {
                                transientRetryCount++;
                                Thread.Sleep(1);
                                continue;
                            }

                            throw new IOException(SR.Format(SR.IO_SharedMemory_FilePermissionsIncorrect, sharedMemoryFilePath, PermissionsMask_OwnerUser_ReadWrite));
                        }
                    }
                    createdFile = false;
                    return fd;
                }

                if (error.Error == Interop.Error.EACCES && transientRetryCount < TransientRetryCount)
                {
                    // During creation, the mode passed to O_CREAT is filtered by process umask. Retry briefly in case
                    // another process is still in the window between creating the file and fixing its permissions.
                    transientRetryCount++;
                    Thread.Sleep(1);
                    continue;
                }

                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();
                    fd.Dispose();
                    if (error.Error == Interop.Error.EEXIST && transientRetryCount < TransientRetryCount)
                    {
                        // Another process created the file between our ENOENT check and our O_CREAT|O_EXCL attempt.
                        // Retry opening the existing file.
                        transientRetryCount++;
                        continue;
                    }
                    throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath);
                }

                // The mode passed to open(..., O_CREAT|O_EXCL, permissionsMask) is filtered by process umask, so the
                // created file may not have the requested access. Set the exact mode explicitly.
                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)
        {
            int transientRetryCount = 0;
            while (true)
            {
                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. The validation below tolerates that transient state while using mkdir() on the final path avoids
                    // rename() replacing an existing empty directory that another process may already be locking.

                    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
                        {
                            try { Directory.Delete(directoryPath); } catch { }
                            throw;
                        }

                        return true;
                    }

                    if (Interop.Sys.MkDir(directoryPath, (int)permissionsMask) == 0)
                    {
                        try
                        {
                            FileSystem.SetUnixFileMode(directoryPath, permissionsMask);
                        }
                        catch
                        {
                            try { Directory.Delete(directoryPath); } catch { }
                            throw;
                        }

                        return true;
                    }

                    Interop.ErrorInfo mkdirError = Interop.Sys.GetLastErrorInfo();
                    if (mkdirError.Error != Interop.Error.EEXIST)
                    {
                        throw Interop.GetExceptionForIoErrno(mkdirError, directoryPath);
                    }

                    statResult = Interop.Sys.Stat(directoryPath, out fileStatus);
                }

                if (statResult != 0)
                {
                    Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo();
                    // While another process is creating this path, stat() can temporarily fail with ENOENT before the
                    // directory appears, or with EACCES before the creating process has finished normalizing directory
                    // permissions for cross-user access.
                    if ((error.Error == Interop.Error.ENOENT || error.Error == Interop.Error.EACCES) && RetryOnTransientPermissionFailure())
                    {
                        continue;
                    }

                    throw Interop.GetExceptionForIoErrno(error, directoryPath);
                }

                // If the path exists, check that it's a directory
                if ((fileStatus.Mode & Interop.Sys.FileTypes.S_IFDIR) == 0)
                {
                    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;
                    }

                    if (RetryOnTransientPermissionFailure())
                    {
                        continue;
                    }

                    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)
                {
                    if (RetryOnTransientPermissionFailure())
                    {
                        continue;
                    }

                    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)
                {
                    if (RetryOnTransientPermissionFailure())
                    {
                        continue;
                    }

                    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)
                {
                    return true;
                }

                if (RetryOnTransientPermissionFailure())
                {
                    continue;
                }

                throw new IOException(SR.Format(SR.IO_SharedMemory_DirectoryOwnerPermissionsIncorrect, directoryPath, Convert.ToString(fileStatus.Mode, 8)));
            }

            bool RetryOnTransientPermissionFailure()
            {
                if (transientRetryCount >= TransientRetryCount)
                {
                    return false;
                }

                transientRetryCount++;
                Thread.Sleep(1);
                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 void VerifyCreationDeletionProcessLockIsNotLocked()
        {
            _creationDeletionProcessLock.VerifyIsNotLocked();
        }

        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;
        }
    }
}