diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MemfdCreate.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MemfdCreate.cs new file mode 100644 index 00000000000000..5fdca42fdb4aae --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MemfdCreate.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using System.Threading; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class Sys + { + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_MemfdCreate", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)] + internal static partial SafeFileHandle MemfdCreate(string name, int isReadonly); + + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_IsMemfdSupported", SetLastError = true)] + private static partial int MemfdSupportedImpl(); + + private static volatile sbyte s_memfdSupported; + + internal static bool IsMemfdSupported + { + get + { + sbyte memfdSupported = s_memfdSupported; + if (memfdSupported == 0) + { + Interlocked.CompareExchange(ref s_memfdSupported, (sbyte)(MemfdSupportedImpl() == 1 ? 1 : -1), 0); + memfdSupported = s_memfdSupported; + } + return memfdSupported > 0; + } + } + } +} diff --git a/src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj b/src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj index 8c7cd9cfdda07b..d024cdd7d73110 100644 --- a/src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj +++ b/src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj @@ -91,6 +91,8 @@ Link="Common\Interop\Unix\Interop.Libraries.cs" /> + + diff --git a/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Unix.cs b/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Unix.cs index 2c49129705d1f9..ae9e24c5722973 100644 --- a/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Unix.cs +++ b/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Unix.cs @@ -162,11 +162,13 @@ private static FileAccess TranslateProtectionsToFileAccess(Interop.Sys.MemoryMap private static SafeFileHandle CreateSharedBackingObject(Interop.Sys.MemoryMappedProtections protections, long capacity, HandleInheritability inheritability) { - return CreateSharedBackingObjectUsingMemory(protections, capacity, inheritability) - ?? CreateSharedBackingObjectUsingFile(protections, capacity, inheritability); + return Interop.Sys.IsMemfdSupported ? + CreateSharedBackingObjectUsingMemoryMemfdCreate(protections, capacity, inheritability) : + CreateSharedBackingObjectUsingMemoryShmOpen(protections, capacity, inheritability) + ?? CreateSharedBackingObjectUsingFile(protections, capacity, inheritability); } - private static SafeFileHandle? CreateSharedBackingObjectUsingMemory( + private static SafeFileHandle? CreateSharedBackingObjectUsingMemoryShmOpen( Interop.Sys.MemoryMappedProtections protections, long capacity, HandleInheritability inheritability) { // Determine the flags to use when creating the shared memory object @@ -244,27 +246,66 @@ private static SafeFileHandle CreateSharedBackingObject(Interop.Sys.MemoryMapped fd.Dispose(); throw; } + } + + private static string GenerateMapName() + { + // macOS shm_open documentation says that the sys-call can fail with ENAMETOOLONG if the name exceeds SHM_NAME_MAX characters. + // The problem is that SHM_NAME_MAX is not defined anywhere and is not consistent amongst macOS versions (arm64 vs x64 for example). + // It was reported in 2008 (https://lists.apple.com/archives/xcode-users/2008/Apr/msg00523.html), + // but considered to be by design (http://web.archive.org/web/20140109200632/http://lists.apple.com/archives/darwin-development/2003/Mar/msg00244.html). + // According to https://github.com/qt/qtbase/blob/1ed449e168af133184633d174fd7339a13d1d595/src/corelib/kernel/qsharedmemory.cpp#L53-L56 the actual value is 30. + // Some other OSS libs use 32 (we did as well, but it was not enough) or 31, but we prefer 30 just to be extra safe. + const int MaxNameLength = 30; + // The POSIX shared memory object name must begin with '/'. After that we just want something short (30) and unique. + const string NamePrefix = "/dotnet_"; + return string.Create(MaxNameLength, 0, (span, state) => + { + Span guid = stackalloc char[32]; + Guid.NewGuid().TryFormat(guid, out int charsWritten, "N"); + Debug.Assert(charsWritten == 32); + NamePrefix.CopyTo(span); + guid.Slice(0, MaxNameLength - NamePrefix.Length).CopyTo(span.Slice(NamePrefix.Length)); + Debug.Assert(Encoding.UTF8.GetByteCount(span) <= MaxNameLength); // the standard uses Utf8 + }); + } + + private static SafeFileHandle CreateSharedBackingObjectUsingMemoryMemfdCreate( + Interop.Sys.MemoryMappedProtections protections, long capacity, HandleInheritability inheritability) + { + int isReadonly = ((protections & Interop.Sys.MemoryMappedProtections.PROT_READ) != 0 && + (protections & Interop.Sys.MemoryMappedProtections.PROT_WRITE) == 0) ? 1 : 0; - static string GenerateMapName() + SafeFileHandle fd = Interop.Sys.MemfdCreate(GenerateMapName(), isReadonly); + if (fd.IsInvalid) { - // macOS shm_open documentation says that the sys-call can fail with ENAMETOOLONG if the name exceeds SHM_NAME_MAX characters. - // The problem is that SHM_NAME_MAX is not defined anywhere and is not consistent amongst macOS versions (arm64 vs x64 for example). - // It was reported in 2008 (https://lists.apple.com/archives/xcode-users/2008/Apr/msg00523.html), - // but considered to be by design (http://web.archive.org/web/20140109200632/http://lists.apple.com/archives/darwin-development/2003/Mar/msg00244.html). - // According to https://github.com/qt/qtbase/blob/1ed449e168af133184633d174fd7339a13d1d595/src/corelib/kernel/qsharedmemory.cpp#L53-L56 the actual value is 30. - // Some other OSS libs use 32 (we did as well, but it was not enough) or 31, but we prefer 30 just to be extra safe. - const int MaxNameLength = 30; - // The POSIX shared memory object name must begin with '/'. After that we just want something short (30) and unique. - const string NamePrefix = "/dotnet_"; - return string.Create(MaxNameLength, 0, (span, state) => + Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); + fd.Dispose(); + + throw Interop.GetExceptionForIoErrno(errorInfo); + } + + try + { + // Give it the right capacity. We do this directly with ftruncate rather + // than via FileStream.SetLength after the FileStream is created because, on some systems, + // lseek fails on shared memory objects, causing the FileStream to think it's unseekable, + // causing it to preemptively throw from SetLength. + Interop.CheckIo(Interop.Sys.FTruncate(fd, capacity)); + + // SystemNative_MemfdCreate sets CLOEXEC implicitly. If the inheritability requested is Inheritable, remove CLOEXEC. + if (inheritability == HandleInheritability.Inheritable && + Interop.Sys.Fcntl.SetFD(fd, 0) == -1) { - Span guid = stackalloc char[32]; - Guid.NewGuid().TryFormat(guid, out int charsWritten, "N"); - Debug.Assert(charsWritten == 32); - NamePrefix.CopyTo(span); - guid.Slice(0, MaxNameLength - NamePrefix.Length).CopyTo(span.Slice(NamePrefix.Length)); - Debug.Assert(Encoding.UTF8.GetByteCount(span) <= MaxNameLength); // the standard uses Utf8 - }); + throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo()); + } + + return fd; + } + catch + { + fd.Dispose(); + throw; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs index 84199a3d306d28..fb7ff75cb2e6c6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs @@ -8,6 +8,6 @@ namespace System public static partial class Environment { public static long WorkingSet => - (long)(Interop.procfs.TryReadProcessStatusInfo(Interop.procfs.ProcPid.Self, out Interop.procfs.ProcessStatusInfo status) ? status.ResidentSetSize : 0); + (long)(Interop.procfs.TryReadProcessStatusInfo(ProcessId, out Interop.procfs.ProcessStatusInfo status) ? status.ResidentSetSize : 0); } } diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c index f413159430ed6f..2165430d65f56a 100644 --- a/src/native/libs/System.Native/entrypoints.c +++ b/src/native/libs/System.Native/entrypoints.c @@ -61,6 +61,8 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_Close) DllImportEntry(SystemNative_Dup) DllImportEntry(SystemNative_Unlink) + DllImportEntry(SystemNative_IsMemfdSupported) + DllImportEntry(SystemNative_MemfdCreate) DllImportEntry(SystemNative_ShmOpen) DllImportEntry(SystemNative_ShmUnlink) DllImportEntry(SystemNative_GetReadDirRBufferSize) diff --git a/src/native/libs/System.Native/pal_io.c b/src/native/libs/System.Native/pal_io.c index 0b6eab7e8c22bd..17e5f77d2d8427 100644 --- a/src/native/libs/System.Native/pal_io.c +++ b/src/native/libs/System.Native/pal_io.c @@ -10,8 +10,8 @@ #include "pal_types.h" #include -#include #include +#include #include #include #include @@ -369,6 +369,72 @@ int32_t SystemNative_Unlink(const char* path) return result; } +#ifdef __NR_memfd_create +#ifndef MFD_CLOEXEC +#define MFD_CLOEXEC 0x0001U +#endif +#ifndef MFD_ALLOW_SEALING +#define MFD_ALLOW_SEALING 0x0002U +#endif +#ifndef F_ADD_SEALS +#define F_ADD_SEALS (1024 + 9) +#endif +#ifndef F_SEAL_WRITE +#define F_SEAL_WRITE 0x0008 +#endif +#endif + +int32_t SystemNative_IsMemfdSupported(void) +{ +#ifdef __NR_memfd_create +#ifdef TARGET_LINUX + struct utsname uts; + int32_t major, minor; + + // memfd_create is known to only work properly on kernel version > 3.17. + // On earlier versions, it may raise SIGSEGV instead of returning ENOTSUP. + if (uname(&uts) == 0 && sscanf(uts.release, "%d.%d", &major, &minor) == 2 && (major < 3 || (major == 3 && minor < 17))) + { + return 0; + } +#endif + + // Note that the name has no affect on file descriptor behavior. From linux manpage: + // Names do not affect the behavior of the file descriptor, and as such multiple files can have the same name without any side effects. + int32_t fd = (int32_t)syscall(__NR_memfd_create, "test", MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (fd < 0) return 0; + + close(fd); + return 1; +#else + errno = ENOTSUP; + return 0; +#endif +} + +intptr_t SystemNative_MemfdCreate(const char* name, int32_t isReadonly) +{ +#ifdef __NR_memfd_create +#if defined(SHM_NAME_MAX) // macOS + assert(strlen(name) <= SHM_NAME_MAX); +#elif defined(PATH_MAX) // other Unixes + assert(strlen(name) <= PATH_MAX); +#endif + + int32_t fd = (int32_t)syscall(__NR_memfd_create, name, MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (!isReadonly || fd < 0) return fd; + + // Add a write seal when readonly protection requested + while (fcntl(fd, F_ADD_SEALS, F_SEAL_WRITE) < 0 && errno == EINTR); + return fd; +#else + (void)name; + (void)isReadonly; + errno = ENOTSUP; + return -1; +#endif +} + intptr_t SystemNative_ShmOpen(const char* name, int32_t flags, int32_t mode) { #if defined(SHM_NAME_MAX) // macOS diff --git a/src/native/libs/System.Native/pal_io.h b/src/native/libs/System.Native/pal_io.h index 03fd94cea25417..a07aa7c170219a 100644 --- a/src/native/libs/System.Native/pal_io.h +++ b/src/native/libs/System.Native/pal_io.h @@ -369,6 +369,20 @@ PALEXPORT intptr_t SystemNative_Dup(intptr_t oldfd); */ PALEXPORT int32_t SystemNative_Unlink(const char* path); +/** + * Check if the system supports memfd_create(2). + * + * Returns 1 if memfd_create is supported, 0 if not supported, or -1 on failure. Sets errno on failure. + */ +PALEXPORT int32_t SystemNative_IsMemfdSupported(void); + +/** + * Create an anonymous file descriptor. Implemented as shim to memfd_create(2). + * + * Returns file descriptor or -1 on failure. Sets errno on failure. + */ +PALEXPORT intptr_t SystemNative_MemfdCreate(const char* name, int32_t isReadonly); + /** * Open or create a shared memory object. Implemented as shim to shm_open(3). *