From 1e8807e053cbc86872bfa9050574ed0589c61656 Mon Sep 17 00:00:00 2001 From: springmin Date: Thu, 27 Aug 2026 17:30:53 +0800 Subject: [PATCH 1/5] Handle read-only /tmp and missing NUMA/robust-mutex on HarmonyOS HarmonyOS (OpenHarmony) app sandboxes differ from a plain Linux environment in three ways that break .NET at startup or at runtime: 1. get_mempolicy/mbind are blocked by the seccomp policy, so the GC's NUMA probe SIGSYS-crashes the process. Compile the NUMA syscalls out for TARGET_OHOS; the GC falls back to single-node, which is correct for phones. 2. /tmp is mounted read-only. Shared-memory files (named mutexes, memory-mapped files) now live under Path.GetTempPath() instead of the hardcoded /tmp/, honoring TMPDIR like the rest of the runtime already does. 3. The sysroot's pthread lacks robust-mutex support, so NamedMutex falls back to the shared-memory-file implementation (already the path for OpenBSD/Haiku). Adds internal OperatingSystem.IsOhos() (compile-time TARGET_OHOS, mirroring IsHaiku()) and the TARGET_OHOS managed define. All changes are no-ops on existing platforms. --- src/coreclr/gc/unix/numasupport.cpp | 10 +++++----- .../src/System.Private.CoreLib.Shared.projitems | 1 + .../src/System/IO/SharedMemoryManager.Unix.cs | 8 +++++++- .../src/System/OperatingSystem.cs | 11 +++++++++++ .../src/System/Threading/NamedMutex.Unix.cs | 2 +- 5 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/coreclr/gc/unix/numasupport.cpp b/src/coreclr/gc/unix/numasupport.cpp index 731bc7c0b0d31e..9d466572fb5642 100644 --- a/src/coreclr/gc/unix/numasupport.cpp +++ b/src/coreclr/gc/unix/numasupport.cpp @@ -12,7 +12,7 @@ #include #include -#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) +#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) && !defined(TARGET_OHOS) #include #endif @@ -21,7 +21,7 @@ int g_highestNumaNode = 0; // Is numa available bool g_numaAvailable = false; -#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) +#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) && !defined(TARGET_OHOS) static int GetNodeNum(const char* path, bool firstOnly) { DIR *dir; @@ -56,7 +56,7 @@ static int GetNodeNum(const char* path, bool firstOnly) void NUMASupportInitialize() { -#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) +#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) && !defined(TARGET_OHOS) if (syscall(__NR_get_mempolicy, NULL, NULL, 0, 0, 0) < 0) return; @@ -72,7 +72,7 @@ void NUMASupportInitialize() int GetNumaNodeNumByCpu(int cpu) { -#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) +#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) && !defined(TARGET_OHOS) char path[64]; if (snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d", cpu) < 0) return -1; @@ -85,7 +85,7 @@ int GetNumaNodeNumByCpu(int cpu) long BindMemoryPolicy(void* start, unsigned long len, const unsigned long* nodemask, unsigned long maxnode) { -#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) +#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) && !defined(TARGET_OHOS) return syscall(__NR_mbind, (long)start, len, 1, (long)nodemask, maxnode, 0); #else return -1; diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 414f29f6c9e8c1..c7f1b595ac66cc 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -54,6 +54,7 @@ $(DefineConstants);TARGET_ILLUMOS $(DefineConstants);TARGET_SOLARIS $(DefineConstants);TARGET_HAIKU + $(DefineConstants);TARGET_OHOS $(DefineConstants);FEATURE_CROSS_PROCESS_MUTEX diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/SharedMemoryManager.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/SharedMemoryManager.Unix.cs index f33075278d6648..25cd5412a358f6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/SharedMemoryManager.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/SharedMemoryManager.Unix.cs @@ -414,7 +414,13 @@ private static string InitalizeSharedFilesPath() } } - return "/tmp/"; + // On non-Apple platforms, shared memory files live under the process temp directory. + // Use Path.GetTempPath() (which honors TMPDIR on Unix and falls back to /tmp) rather + // than a hardcoded /tmp: sandboxed environments such as OpenHarmony mount /tmp read-only + // and rely on TMPDIR pointing at a writable location for the .NET shared memory files + // (named mutexes, etc.). Everything else in the runtime already honors TMPDIR, so this + // was the only offender. + return Path.GetTempPath(); } internal static SafeFileHandle CreateOrOpenFile(string sharedMemoryFilePath, SharedMemoryId id, bool createIfNotExist, out bool createdFile) diff --git a/src/libraries/System.Private.CoreLib/src/System/OperatingSystem.cs b/src/libraries/System.Private.CoreLib/src/System/OperatingSystem.cs index 28ecea26fca530..d858f28141b430 100644 --- a/src/libraries/System.Private.CoreLib/src/System/OperatingSystem.cs +++ b/src/libraries/System.Private.CoreLib/src/System/OperatingSystem.cs @@ -220,6 +220,17 @@ internal static bool IsHaiku() => false; #endif + /// + /// Indicates whether the current application is running on HarmonyOS (OpenHarmony). + /// + [NonVersionable] + internal static bool IsOhos() => +#if TARGET_OHOS + true; +#else + false; +#endif + /// /// Indicates whether the current application is running on Android. /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs index 077f68c63b23e2..307159c328182b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs @@ -82,7 +82,7 @@ internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader< // independently by the processes involved. See https://github.com/dotnet/runtime/issues/10519. // On OpenBSD, cross process mutexes are not supported in the pthread implementation. See https://github.com/dotnet/runtime/pull/125089. // On Haiku, robust mutexes are WIP. See https://github.com/dotnet/runtime/pull/126701#issuecomment-4334338213. - private static bool UsePThreadMutexes => !OperatingSystem.IsApplePlatform() && !OperatingSystem.IsFreeBSD() && !OperatingSystem.IsOpenBSD() && !OperatingSystem.IsHaiku(); + private static bool UsePThreadMutexes => !OperatingSystem.IsApplePlatform() && !OperatingSystem.IsFreeBSD() && !OperatingSystem.IsOpenBSD() && !OperatingSystem.IsHaiku() && !OperatingSystem.IsOhos(); private readonly SharedMemoryProcessDataHeader _processDataHeader = header; protected nuint _lockCount; From c00857406e0d88077d81efdc07231d2b6dea98cd Mon Sep 17 00:00:00 2001 From: springmin Date: Thu, 27 Aug 2026 23:13:07 +0800 Subject: [PATCH 2/5] Scope shared-memory path change to HarmonyOS and keep NamedMutex tests in sync --- .../src/System/IO/SharedMemoryManager.Unix.cs | 12 +++++++----- src/libraries/System.Threading/tests/MutexTests.cs | 11 ++++++++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/SharedMemoryManager.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/SharedMemoryManager.Unix.cs index 25cd5412a358f6..bab7ce4aef51e2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/SharedMemoryManager.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/SharedMemoryManager.Unix.cs @@ -415,12 +415,14 @@ private static string InitalizeSharedFilesPath() } // On non-Apple platforms, shared memory files live under the process temp directory. - // Use Path.GetTempPath() (which honors TMPDIR on Unix and falls back to /tmp) rather - // than a hardcoded /tmp: sandboxed environments such as OpenHarmony mount /tmp read-only - // and rely on TMPDIR pointing at a writable location for the .NET shared memory files - // (named mutexes, etc.). Everything else in the runtime already honors TMPDIR, so this - // was the only offender. +#if TARGET_OHOS + // HarmonyOS (OpenHarmony) app sandboxes mount /tmp read-only, so use the process temp + // directory (which honors TMPDIR on Unix and falls back to /tmp) for the .NET shared + // memory files (named mutexes, etc.). return Path.GetTempPath(); +#else + return "/tmp/"; +#endif } internal static SafeFileHandle CreateOrOpenFile(string sharedMemoryFilePath, SharedMemoryId id, bool createIfNotExist, out bool createdFile) diff --git a/src/libraries/System.Threading/tests/MutexTests.cs b/src/libraries/System.Threading/tests/MutexTests.cs index 3354ac0f587e1c..9a7385c615ca5e 100644 --- a/src/libraries/System.Threading/tests/MutexTests.cs +++ b/src/libraries/System.Threading/tests/MutexTests.cs @@ -1039,7 +1039,16 @@ public void NamedMutex_OtherEvent_NotCompatible() Assert.Throws(() => WaitHandle.WaitAny(new WaitHandle[] { m, mre }, 0)); } - private const string GlobalSharedMemoryDirectory = $"/tmp/.dotnet/shm/global"; + // The runtime places global shared memory files under {SharedFilesPath}/.dotnet/shm/global + // (see SharedMemoryManager.Unix.cs). SharedFilesPath is /tmp/ on non-Apple platforms except + // on HarmonyOS, where it honors TMPDIR. Mirror the runtime's derivation so these tests + // always target the same directory the runtime uses. + private static string GlobalSharedMemoryDirectory => +#if TARGET_OHOS + Path.Combine(Path.GetTempPath(), ".dotnet", "shm", "global"); +#else + "/tmp/.dotnet/shm/global"; +#endif private const UnixFileMode AllUsersRwx = UnixFileMode.UserRead | UnixFileMode.UserWrite From 955126211cc425aa3bbeaa0232d5de56ace5698e Mon Sep 17 00:00:00 2001 From: springmin Date: Fri, 28 Aug 2026 12:49:33 +0800 Subject: [PATCH 3/5] Rename TargetsLinuxOhos to TargetsOhos to match TARGET_OHOS convention --- .../src/System.Private.CoreLib.Shared.projitems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index c7f1b595ac66cc..3973be0b12a2c5 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -54,7 +54,7 @@ $(DefineConstants);TARGET_ILLUMOS $(DefineConstants);TARGET_SOLARIS $(DefineConstants);TARGET_HAIKU - $(DefineConstants);TARGET_OHOS + $(DefineConstants);TARGET_OHOS $(DefineConstants);FEATURE_CROSS_PROCESS_MUTEX From cc9ccb34d9ea222e25fbb58a3079ead48e5b48c4 Mon Sep 17 00:00:00 2001 From: springmin Date: Sun, 30 Aug 2026 23:44:21 +0800 Subject: [PATCH 4/5] Rename OHOS identifiers to OPENHARMONY per review feedback --- src/coreclr/gc/unix/numasupport.cpp | 10 +++++----- .../src/System.Private.CoreLib.Shared.projitems | 2 +- .../src/System/IO/SharedMemoryManager.Unix.cs | 2 +- .../src/System/OperatingSystem.cs | 4 ++-- .../src/System/Threading/NamedMutex.Unix.cs | 2 +- src/libraries/System.Threading/tests/MutexTests.cs | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/coreclr/gc/unix/numasupport.cpp b/src/coreclr/gc/unix/numasupport.cpp index 9d466572fb5642..409dbb4a30eb01 100644 --- a/src/coreclr/gc/unix/numasupport.cpp +++ b/src/coreclr/gc/unix/numasupport.cpp @@ -12,7 +12,7 @@ #include #include -#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) && !defined(TARGET_OHOS) +#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) && !defined(TARGET_OPENHARMONY) #include #endif @@ -21,7 +21,7 @@ int g_highestNumaNode = 0; // Is numa available bool g_numaAvailable = false; -#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) && !defined(TARGET_OHOS) +#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) && !defined(TARGET_OPENHARMONY) static int GetNodeNum(const char* path, bool firstOnly) { DIR *dir; @@ -56,7 +56,7 @@ static int GetNodeNum(const char* path, bool firstOnly) void NUMASupportInitialize() { -#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) && !defined(TARGET_OHOS) +#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) && !defined(TARGET_OPENHARMONY) if (syscall(__NR_get_mempolicy, NULL, NULL, 0, 0, 0) < 0) return; @@ -72,7 +72,7 @@ void NUMASupportInitialize() int GetNumaNodeNumByCpu(int cpu) { -#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) && !defined(TARGET_OHOS) +#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) && !defined(TARGET_OPENHARMONY) char path[64]; if (snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d", cpu) < 0) return -1; @@ -85,7 +85,7 @@ int GetNumaNodeNumByCpu(int cpu) long BindMemoryPolicy(void* start, unsigned long len, const unsigned long* nodemask, unsigned long maxnode) { -#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) && !defined(TARGET_OHOS) +#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) && !defined(TARGET_OPENHARMONY) return syscall(__NR_mbind, (long)start, len, 1, (long)nodemask, maxnode, 0); #else return -1; diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 3973be0b12a2c5..85d73d2d0458b4 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -54,7 +54,7 @@ $(DefineConstants);TARGET_ILLUMOS $(DefineConstants);TARGET_SOLARIS $(DefineConstants);TARGET_HAIKU - $(DefineConstants);TARGET_OHOS + $(DefineConstants);TARGET_OPENHARMONY $(DefineConstants);FEATURE_CROSS_PROCESS_MUTEX diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/SharedMemoryManager.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/SharedMemoryManager.Unix.cs index bab7ce4aef51e2..9646fef949e5c6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/SharedMemoryManager.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/SharedMemoryManager.Unix.cs @@ -415,7 +415,7 @@ private static string InitalizeSharedFilesPath() } // On non-Apple platforms, shared memory files live under the process temp directory. -#if TARGET_OHOS +#if TARGET_OPENHARMONY // HarmonyOS (OpenHarmony) app sandboxes mount /tmp read-only, so use the process temp // directory (which honors TMPDIR on Unix and falls back to /tmp) for the .NET shared // memory files (named mutexes, etc.). diff --git a/src/libraries/System.Private.CoreLib/src/System/OperatingSystem.cs b/src/libraries/System.Private.CoreLib/src/System/OperatingSystem.cs index d858f28141b430..6cd0114927a258 100644 --- a/src/libraries/System.Private.CoreLib/src/System/OperatingSystem.cs +++ b/src/libraries/System.Private.CoreLib/src/System/OperatingSystem.cs @@ -224,8 +224,8 @@ internal static bool IsHaiku() => /// Indicates whether the current application is running on HarmonyOS (OpenHarmony). /// [NonVersionable] - internal static bool IsOhos() => -#if TARGET_OHOS + internal static bool IsOpenHarmony() => +#if TARGET_OPENHARMONY true; #else false; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs index 307159c328182b..d43fa784d0ff31 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs @@ -82,7 +82,7 @@ internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader< // independently by the processes involved. See https://github.com/dotnet/runtime/issues/10519. // On OpenBSD, cross process mutexes are not supported in the pthread implementation. See https://github.com/dotnet/runtime/pull/125089. // On Haiku, robust mutexes are WIP. See https://github.com/dotnet/runtime/pull/126701#issuecomment-4334338213. - private static bool UsePThreadMutexes => !OperatingSystem.IsApplePlatform() && !OperatingSystem.IsFreeBSD() && !OperatingSystem.IsOpenBSD() && !OperatingSystem.IsHaiku() && !OperatingSystem.IsOhos(); + private static bool UsePThreadMutexes => !OperatingSystem.IsApplePlatform() && !OperatingSystem.IsFreeBSD() && !OperatingSystem.IsOpenBSD() && !OperatingSystem.IsHaiku() && !OperatingSystem.IsOpenHarmony(); private readonly SharedMemoryProcessDataHeader _processDataHeader = header; protected nuint _lockCount; diff --git a/src/libraries/System.Threading/tests/MutexTests.cs b/src/libraries/System.Threading/tests/MutexTests.cs index 9a7385c615ca5e..e7ec25c3406685 100644 --- a/src/libraries/System.Threading/tests/MutexTests.cs +++ b/src/libraries/System.Threading/tests/MutexTests.cs @@ -1044,7 +1044,7 @@ public void NamedMutex_OtherEvent_NotCompatible() // on HarmonyOS, where it honors TMPDIR. Mirror the runtime's derivation so these tests // always target the same directory the runtime uses. private static string GlobalSharedMemoryDirectory => -#if TARGET_OHOS +#if TARGET_OPENHARMONY Path.Combine(Path.GetTempPath(), ".dotnet", "shm", "global"); #else "/tmp/.dotnet/shm/global"; From 4a5ae9e94f9f54d162e412e556099c541dcf8dad Mon Sep 17 00:00:00 2001 From: springmin Date: Mon, 31 Aug 2026 09:16:12 +0800 Subject: [PATCH 5/5] Use TargetsOpenHarmony (plural) per jkotas review Matches TargetsAndroid/TargetsLinuxGlibc naming pattern. --- .../src/System.Private.CoreLib.Shared.projitems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 85d73d2d0458b4..3f23195a6d8d23 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -54,7 +54,7 @@ $(DefineConstants);TARGET_ILLUMOS $(DefineConstants);TARGET_SOLARIS $(DefineConstants);TARGET_HAIKU - $(DefineConstants);TARGET_OPENHARMONY + $(DefineConstants);TARGET_OPENHARMONY $(DefineConstants);FEATURE_CROSS_PROCESS_MUTEX