From 15e49578925525d4ba3057b3c4dee58733333add Mon Sep 17 00:00:00 2001
From: Adeel <3840695+am11@users.noreply.github.com>
Date: Sat, 20 Jul 2024 22:54:11 +0300
Subject: [PATCH] Use memfd_create when available
---
.../Unix/System.Native/Interop.MemfdCreate.cs | 34 +++++++++++++++
.../src/System.IO.MemoryMappedFiles.csproj | 4 ++
.../MemoryMappedFile.Unix.cs | 22 +++++++---
.../src/System/Environment.SunOS.cs | 2 +-
src/native/libs/Common/pal_config.h.in | 1 +
src/native/libs/System.Native/entrypoints.c | 2 +
src/native/libs/System.Native/pal_io.c | 42 +++++++++++++++++++
src/native/libs/System.Native/pal_io.h | 14 +++++++
src/native/libs/configure.cmake | 4 ++
9 files changed, 118 insertions(+), 7 deletions(-)
create mode 100644 src/libraries/Common/src/Interop/Unix/System.Native/Interop.MemfdCreate.cs
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..f323677e5788ee
--- /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);
+
+ [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_MemfdSupported", SetLastError = true)]
+ private static partial int MemfdSupportedImpl();
+
+ private static volatile sbyte s_memfdSupported;
+
+ internal static bool MemfdSupported
+ {
+ 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..c4c13e3703fd25 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
@@ -190,7 +190,14 @@ private static SafeFileHandle CreateSharedBackingObject(Interop.Sys.MemoryMapped
do
{
mapName = GenerateMapName();
- fd = Interop.Sys.ShmOpen(mapName, flags, (int)perms); // Create the shared memory object.
+ if (Interop.Sys.MemfdSupported)
+ {
+ fd = Interop.Sys.MemfdCreate(mapName);
+ }
+ else
+ {
+ fd = Interop.Sys.ShmOpen(mapName, flags, (int)perms); // Create the shared memory object.
+ }
if (fd.IsInvalid)
{
@@ -204,7 +211,7 @@ private static SafeFileHandle CreateSharedBackingObject(Interop.Sys.MemoryMapped
// the result of native shm_open does not work well with our subsequent call to mmap.
return null;
}
- else if (errorInfo.Error == Interop.Error.ENAMETOOLONG)
+ else if (!Interop.Sys.MemfdSupported && errorInfo.Error == Interop.Error.ENAMETOOLONG)
{
Debug.Fail($"shm_open failed with ENAMETOOLONG for {Encoding.UTF8.GetByteCount(mapName)} byte long name.");
// in theory it should not happen anymore, but just to be extra safe we use the fallback
@@ -219,10 +226,13 @@ private static SafeFileHandle CreateSharedBackingObject(Interop.Sys.MemoryMapped
try
{
- // Unlink the shared memory object immediately so that it'll go away once all handles
- // to it are closed (as with opened then unlinked files, it'll remain usable via
- // the open handles even though it's unlinked and can't be opened anew via its name).
- Interop.CheckIo(Interop.Sys.ShmUnlink(mapName));
+ if (!Interop.Sys.MemfdSupported)
+ {
+ // Unlink the shared memory object immediately so that it'll go away once all handles
+ // to it are closed (as with opened then unlinked files, it'll remain usable via
+ // the open handles even though it's unlinked and can't be opened anew via its name).
+ Interop.CheckIo(Interop.Sys.ShmUnlink(mapName));
+ }
// 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,
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/Common/pal_config.h.in b/src/native/libs/Common/pal_config.h.in
index 0960f6aa971c8e..211bd2949ae42f 100644
--- a/src/native/libs/Common/pal_config.h.in
+++ b/src/native/libs/Common/pal_config.h.in
@@ -11,6 +11,7 @@
#cmakedefine01 HAVE_F_DUPFD
#cmakedefine01 HAVE_F_FULLFSYNC
#cmakedefine01 HAVE_O_CLOEXEC
+#cmakedefine01 HAVE_MEMFD_CREATE
#cmakedefine01 HAVE_GETIFADDRS
#cmakedefine01 HAVE_UTSNAME_DOMAINNAME
#cmakedefine01 HAVE_STAT64
diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c
index 51c761109159b5..d9f9b4d537b0ad 100644
--- a/src/native/libs/System.Native/entrypoints.c
+++ b/src/native/libs/System.Native/entrypoints.c
@@ -62,6 +62,8 @@ static const Entry s_sysNative[] =
DllImportEntry(SystemNative_Close)
DllImportEntry(SystemNative_Dup)
DllImportEntry(SystemNative_Unlink)
+ DllImportEntry(SystemNative_MemfdSupported)
+ 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 4051656d35ac07..8a0b85d79f844a 100644
--- a/src/native/libs/System.Native/pal_io.c
+++ b/src/native/libs/System.Native/pal_io.c
@@ -369,6 +369,48 @@ int32_t SystemNative_Unlink(const char* path)
return result;
}
+int32_t SystemNative_MemfdSupported(void)
+{
+#if HAVE_MEMFD_CREATE
+#ifdef TARGET_LINUX
+ struct utsname uts;
+ int32_t major, minor;
+
+ // memfd_create is only known to work properly on kernel version > 3.17 and throws SIGSEGV instead of ENOTSUP
+ if (uname(&uts) == 0 && sscanf(uts.release, "%d.%d", &major, &minor) == 2 && (major < 3 || (major == 3 && minor < 17)))
+ {
+ return 0;
+ }
+#endif
+
+ int32_t fd = 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)
+{
+#if HAVE_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
+
+ return memfd_create(name, MFD_CLOEXEC | MFD_ALLOW_SEALING);
+#else
+ (void)name;
+ 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..a5a8261e569372 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_MemfdSupported(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);
+
/**
* Open or create a shared memory object. Implemented as shim to shm_open(3).
*
diff --git a/src/native/libs/configure.cmake b/src/native/libs/configure.cmake
index 871e5bd2ea8099..07f7918406d920 100644
--- a/src/native/libs/configure.cmake
+++ b/src/native/libs/configure.cmake
@@ -137,6 +137,10 @@ check_symbol_exists(
fcntl.h
HAVE_F_FULLFSYNC)
+check_function_exists(
+ memfd_create
+ HAVE_MEMFD_CREATE)
+
check_function_exists(
getifaddrs
HAVE_GETIFADDRS)