From 764107f52c29f508fddd4e083b78da558f315d4f Mon Sep 17 00:00:00 2001 From: Trung Nguyen <57174311+trungnt2910@users.noreply.github.com> Date: Sat, 22 Nov 2025 01:57:16 +1100 Subject: [PATCH 1/4] Haiku: Process/thread management functions Add support for process/thread management functions in `System.Diagnostics.Process` for Haiku. This is required to build managed runtime libraries for Haiku as well as running a simple "Hello, World!" application. Co-authored-by: Jessica Hamilton --- .../Common/src/Interop/Haiku/Interop.Image.cs | 42 +++ .../Common/src/Interop/Haiku/Interop.OS.cs | 146 +++++++++- .../src/System.Diagnostics.Process.csproj | 12 +- .../src/System/Diagnostics/Process.Haiku.cs | 196 +++++++++++++ .../src/System/Diagnostics/Process.cs | 2 + .../Diagnostics/ProcessManager.Haiku.cs | 192 +++++++++++++ .../System/Diagnostics/ProcessThread.Haiku.cs | 129 +++++++++ .../src/System/Diagnostics/ProcessThread.cs | 2 + src/native/libs/Common/pal_config.h.in | 1 + src/native/libs/System.Native/entrypoints.c | 9 + src/native/libs/System.Native/pal_getosinfo.c | 261 ++++++++++++++++++ src/native/libs/System.Native/pal_getosinfo.h | 51 ++++ src/native/libs/configure.cmake | 4 + 13 files changed, 1042 insertions(+), 5 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Haiku/Interop.Image.cs create mode 100644 src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Haiku.cs create mode 100644 src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Haiku.cs create mode 100644 src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessThread.Haiku.cs diff --git a/src/libraries/Common/src/Interop/Haiku/Interop.Image.cs b/src/libraries/Common/src/Interop/Haiku/Interop.Image.cs new file mode 100644 index 00000000000000..8394eb2f4c36bf --- /dev/null +++ b/src/libraries/Common/src/Interop/Haiku/Interop.Image.cs @@ -0,0 +1,42 @@ +// 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; + +#pragma warning disable CA1823 // analyzer incorrectly flags fixed buffer length const (https://github.com/dotnet/roslyn/issues/37593) + +internal static partial class Interop +{ + internal static partial class Image + { + internal const int MAXPATHLEN = 1024; + + internal enum ImageType : int + { + B_APP_IMAGE = 1, + B_LIBRARY_IMAGE, + B_ADD_ON_IMAGE, + B_SYSTEM_IMAGE, + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct ImageInfo + { + public ImageType type; + public fixed byte name[MAXPATHLEN]; + public void* text; + public int text_size; + public int data_size; + } + + /// + /// Gets information about images owned by a team. + /// + /// The team ID to iterate. + /// A cookie to track the iteration. + /// The structure to fill in. + /// Returns 0 on success. Returns an error code on failure or when there are no more images to iterate. + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetNextImageInfo")] + internal static partial int GetNextImageInfo(int team, ref int cookie, out ImageInfo info); + } +} diff --git a/src/libraries/Common/src/Interop/Haiku/Interop.OS.cs b/src/libraries/Common/src/Interop/Haiku/Interop.OS.cs index 3246a7c45bd0cd..29dd28652fbfe1 100644 --- a/src/libraries/Common/src/Interop/Haiku/Interop.OS.cs +++ b/src/libraries/Common/src/Interop/Haiku/Interop.OS.cs @@ -1,28 +1,166 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Runtime.InteropServices; +#pragma warning disable CA1823 // analyzer incorrectly flags fixed buffer length const (https://github.com/dotnet/roslyn/issues/37593) internal static partial class Interop { internal static partial class OS { + internal const int B_OS_NAME_LENGTH = 32; + [StructLayout(LayoutKind.Sequential)] - internal unsafe struct AreaInfo + internal struct AreaInfo { public nuint size; public uint ram_size; } + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct TeamInfo + { + public int team; + public int session_id; + public int parent; + public fixed byte name[B_OS_NAME_LENGTH]; + public long start_time; + } + + internal enum BTeamUsage : int + { + B_TEAM_USAGE_SELF = 0, + B_TEAM_USAGE_CHILDREN = -1, + } + + [StructLayout(LayoutKind.Sequential)] + internal struct TeamUsageInfo + { + public long user_time; + public long kernel_time; + } + + internal enum ThreadState : int + { + B_THREAD_RUNNING = 1, + B_THREAD_READY, + B_THREAD_RECEIVING, + B_THREAD_ASLEEP, + B_THREAD_SUSPENDED, + B_THREAD_WAITING, + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ThreadInfo + { + public int thread; + public int team; + public ThreadState state; + public int priority; + public long user_time; + public long kernel_time; + } + + internal enum BPriority : int + { + B_IDLE_PRIORITY = 0, + B_LOWEST_ACTIVE_PRIORITY = 1, + B_LOW_PRIORITY = 5, + B_NORMAL_PRIORITY = 10, + B_DISPLAY_PRIORITY = 15, + B_URGENT_DISPLAY_PRIORITY = 20, + B_REAL_TIME_DISPLAY_PRIORITY = 100, + B_URGENT_PRIORITY = 110, + B_REAL_TIME_PRIORITY = 120, + } + + [StructLayout(LayoutKind.Sequential)] + internal struct SystemInfo + { + public long boot_time; + } + /// /// Gets information about areas owned by a team. /// /// The team ID of the areas to iterate. /// A cookie to track the iteration. - /// The structure to fill in. + /// The structure to fill in. /// Returns 0 on success. Returns an error code on failure or when there are no more areas to iterate. [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetNextAreaInfo")] - internal static unsafe partial int GetNextAreaInfo(int team, ref nint cookie, out AreaInfo areaInfo); + internal static partial int GetNextAreaInfo(int team, ref nint cookie, out AreaInfo info); + + /// + /// Gets information about a team. + /// + /// The team ID. + /// The structure to fill in. + /// Returns 0 on success or an error code on failure. + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetTeamInfo")] + internal static partial int GetTeamInfo(int team, out TeamInfo info); + + /// + /// Gets information about teams. + /// + /// A cookie to track the iteration. + /// The structure to fill in. + /// Returns 0 on success. Returns an error code on failure or when there are no more teams to iterate. + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetNextTeamInfo")] + internal static partial int GetNextTeamInfo(ref int cookie, out TeamInfo info); + + /// + /// Gets team IDs. + /// + /// A cookie to track the iteration. + /// The integer to store the retrieved team ID. + /// Returns 0 on success. Returns an error code on failure or when there are no more teams to iterate. + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetNextTeamId")] + internal static partial int GetNextTeamId(ref int cookie, out int team); + + /// + /// Gets information about a team's usage. + /// + /// The team ID. + /// Specifies whether to get usage information for the team or its children. + /// The structure to fill in. + /// Returns 0 on success or an error code on failure. + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetTeamUsageInfo")] + internal static partial int GetTeamUsageInfo(int team, BTeamUsage who, out TeamUsageInfo info); + + /// + /// Sets the priority of a thread. + /// + /// The thread ID. + /// The new priority. + /// The previous priority if successful or an error code on failure. + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_SetThreadPriority")] + internal static partial int SetThreadPriority(int thread, int newPriority); + + /// + /// Gets information about a thread. + /// + /// The thread ID. + /// The structure to fill in. + /// Returns 0 on success or an error code on failure. + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetThreadInfo")] + internal static partial int GetThreadInfo(int thread, out ThreadInfo info); + + /// + /// Gets information about threads owned by a team. + /// + /// The team ID of the threads to iterate. + /// A cookie to track the iteration. + /// The structure to fill in. + /// Returns 0 on success. Returns an error code on failure or when there are no more threads to iterate. + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetNextThreadInfo")] + internal static partial int GetNextThreadInfo(int team, ref int cookie, out ThreadInfo info); + + /// + /// Gets information about the system. + /// + /// The system info to store retrieved information. + /// 0 if successful. + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetSystemInfo")] + internal static partial int GetSystemInfo(out SystemInfo info); } } diff --git a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj index bc1e3b89bb10de..e924bcecff2ddb 100644 --- a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj +++ b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj @@ -1,7 +1,7 @@  - $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-freebsd;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-maccatalyst;$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent)-illumos;$(NetCoreAppCurrent)-solaris;$(NetCoreAppCurrent) + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-freebsd;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-maccatalyst;$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent)-illumos;$(NetCoreAppCurrent)-solaris;$(NetCoreAppCurrent)-haiku;$(NetCoreAppCurrent) $(DefineConstants);FEATURE_REGISTRY true false @@ -403,6 +403,16 @@ Link="Common\Interop\SunOS\procfs\Interop.ProcFs.GetThreadInfoById.cs" /> + + + + + + + + diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Haiku.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Haiku.cs new file mode 100644 index 00000000000000..789c7bb41063e1 --- /dev/null +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Haiku.cs @@ -0,0 +1,196 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using System.Runtime.Versioning; +using System.Threading; + +namespace System.Diagnostics +{ + public partial class Process + { + // bigtime_t represents microseconds while DateTime.Ticks is in 100ns units + private const int BigTimeToTicks = 10; + + private static long s_bootTimeTicks; + /// Gets the system boot time. + private static DateTime BootTime + { + get + { + long bootTimeTicks = Interlocked.Read(ref s_bootTimeTicks); + if (bootTimeTicks == 0) + { + Interop.OS.SystemInfo info; + int status = Interop.OS.GetSystemInfo(out info); + + if (status != 0) + { + throw new Win32Exception(status); + } + + bootTimeTicks = info.boot_time * BigTimeToTicks; + long oldValue = Interlocked.CompareExchange(ref s_bootTimeTicks, bootTimeTicks, 0); + if (oldValue != 0) // a different thread has managed to update the ticks first + { + bootTimeTicks = oldValue; // consistency + } + } + return DateTime.UnixEpoch.AddTicks(bootTimeTicks); + } + } + + /// Gets the time the associated process was started. + internal DateTime StartTimeCore + { + get + { + EnsureState(State.HaveNonExitedId); + + Interop.OS.TeamInfo info; + int status = Interop.OS.GetTeamInfo(_processId, out info); + + if (status != 0) + { + throw new Win32Exception(status); + } + + return BootTime.AddTicks(info.start_time * BigTimeToTicks).ToLocalTime(); + } + } + + /// + /// Gets the amount of time the associated process has spent utilizing the CPU. + /// It is the sum of the and + /// . + /// + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [SupportedOSPlatform("maccatalyst")] + public TimeSpan TotalProcessorTime + { + get + { + EnsureState(State.HaveNonExitedId); + + Interop.OS.TeamUsageInfo info; + int status = Interop.OS.GetTeamUsageInfo(_processId, Interop.OS.BTeamUsage.B_TEAM_USAGE_SELF, out info); + + if (status != 0) + { + throw new Win32Exception(status); + } + + return TimeSpan.FromMicroseconds(info.user_time + info.kernel_time); + } + } + + /// + /// Gets the amount of time the associated process has spent running code + /// inside the application portion of the process (not the operating system core). + /// + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [SupportedOSPlatform("maccatalyst")] + public TimeSpan UserProcessorTime + { + get + { + EnsureState(State.HaveNonExitedId); + + Interop.OS.TeamUsageInfo info; + int status = Interop.OS.GetTeamUsageInfo(_processId, Interop.OS.BTeamUsage.B_TEAM_USAGE_SELF, out info); + + if (status != 0) + { + throw new Win32Exception(status); + } + + return TimeSpan.FromMicroseconds(info.user_time); + } + } + + /// Gets the amount of time the process has spent running code inside the operating system core. + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [SupportedOSPlatform("maccatalyst")] + public TimeSpan PrivilegedProcessorTime + { + get + { + EnsureState(State.HaveNonExitedId); + + Interop.OS.TeamUsageInfo info; + int status = Interop.OS.GetTeamUsageInfo(_processId, Interop.OS.BTeamUsage.B_TEAM_USAGE_SELF, out info); + + if (status != 0) + { + throw new Win32Exception(status); + } + + return TimeSpan.FromMicroseconds(info.kernel_time); + } + } + + /// Gets parent process ID + private unsafe int ParentProcessId + { + get + { + EnsureState(State.HaveNonExitedId); + + Interop.OS.TeamInfo info; + int status = Interop.OS.GetTeamInfo(_processId, out info); + + if (status != 0) + { + throw new Win32Exception(status); + } + + return info.parent; + } + } + + /// + /// Gets or sets which processors the threads in this process can be scheduled to run on. + /// + private static IntPtr ProcessorAffinityCore + { + get { throw new PlatformNotSupportedException(); } + set { throw new PlatformNotSupportedException(); } + } + +#pragma warning disable IDE0060 + /// + /// Make sure we have obtained the min and max working set limits. + /// + private static void GetWorkingSetLimits(out IntPtr minWorkingSet, out IntPtr maxWorkingSet) + { + throw new PlatformNotSupportedException(); + } + + /// Sets one or both of the minimum and maximum working set limits. + /// The new minimum working set limit, or null not to change it. + /// The new maximum working set limit, or null not to change it. + /// The resulting minimum working set limit after any changes applied. + /// The resulting maximum working set limit after any changes applied. + private static void SetWorkingSetLimitsCore(IntPtr? newMin, IntPtr? newMax, out IntPtr resultingMin, out IntPtr resultingMax) + { + throw new PlatformNotSupportedException(); + } +#pragma warning restore IDE0060 + + /// Gets execution path + internal static string? GetPathToOpenFile() + { + if (Interop.Sys.Stat("/boot/system/bin/open", out _) == 0) + { + return "/boot/system/bin/open"; + } + else + { + return null; + } + } + } +} diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs index 8966e578389e92..7ed47855d7ee51 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs @@ -276,6 +276,7 @@ public IntPtr MaxWorkingSet [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("tvos")] [SupportedOSPlatform("maccatalyst")] + [UnsupportedOSPlatform("haiku")] get { EnsureWorkingSetLimits(); @@ -300,6 +301,7 @@ public IntPtr MinWorkingSet [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("tvos")] [SupportedOSPlatform("maccatalyst")] + [UnsupportedOSPlatform("haiku")] get { EnsureWorkingSetLimits(); diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Haiku.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Haiku.cs new file mode 100644 index 00000000000000..54fe41b88fce16 --- /dev/null +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Haiku.cs @@ -0,0 +1,192 @@ +// 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.IO; + +namespace System.Diagnostics +{ + internal static partial class ProcessManager + { + /// Gets process infos for each process on the local machine. + /// The builder to add found process infos to. + /// Optional process name to use as an inclusion filter. + public static void GetProcessInfos(ref ArrayBuilder builder, string? processNameFilter) + { + int cookie = 0; + Interop.OS.TeamInfo info; + + while ((Interop.OS.GetNextTeamInfo(ref cookie, out info)) == 0) + { + ProcessInfo? pi = GetProcessInfoFromTeamInfo(ref info, processNameFilter); + if (pi != null) + { + builder.Add(pi); + } + } + } + + /// Gets an array of module infos for the specified process. + /// The ID of the process whose modules should be enumerated. + /// The array of modules. + internal static unsafe ProcessModuleCollection GetModules(int processId) + { + ProcessModuleCollection modules = new ProcessModuleCollection(0); + int cookie = 0; + Interop.Image.ImageInfo info; + + while ((Interop.Image.GetNextImageInfo(processId, ref cookie, out info)) == 0) + { + string modulePath = GetString(info.name, Interop.Image.MAXPATHLEN); + + var procModule = new ProcessModule(modulePath, Path.GetFileName(modulePath)) + { + BaseAddress = (nint)info.text, + ModuleMemorySize = info.text_size + info.data_size, + EntryPointAddress = IntPtr.Zero // unknown + }; + + if (info.type == Interop.Image.ImageType.B_APP_IMAGE) + { + modules.Insert(0, procModule); + } + else + { + modules.Add(procModule); + } + } + + return modules; + } + + internal static string? GetProcessName(int processId, string _ /* machineName */, bool __ /* isRemoteMachine */, ref ProcessInfo? processInfo) + { + if (processInfo is not null) + { + return processInfo.ProcessName; + } + + processInfo = CreateProcessInfo(processId); + return processInfo?.ProcessName; + } + + internal static ProcessInfo? CreateProcessInfo(int pid, string? processNameFilter = null) + { + // Negative PIDs aren't valid + ArgumentOutOfRangeException.ThrowIfNegative(pid); + + Interop.OS.TeamInfo info; + int status = Interop.OS.GetTeamInfo(pid, out info); + + if (status != 0) + { + return null; + } + + return GetProcessInfoFromTeamInfo(ref info, processNameFilter); + } + + // ---------------------------------- + // ---- Unix PAL layer ends here ---- + // ---------------------------------- + + private static unsafe ProcessInfo? GetProcessInfoFromTeamInfo(ref Interop.OS.TeamInfo info, string? processNameFilter = null) + { + string processName; + + fixed (byte* p = info.name) + { + processName = GetString(p, Interop.OS.B_OS_NAME_LENGTH); + } + + if (!string.IsNullOrEmpty(processNameFilter) && + !processNameFilter.Equals(processName, StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + var procInfo = new ProcessInfo() + { + ProcessId = info.team, + ProcessName = processName, + SessionId = info.session_id + }; + + { + nint cookie = 0; + Interop.OS.AreaInfo areaInfo; + + while (Interop.OS.GetNextAreaInfo(info.team, ref cookie, out areaInfo) == 0) + { + procInfo.VirtualBytes += (long)areaInfo.size; + procInfo.WorkingSet += areaInfo.ram_size; + } + } + + { + int cookie = 0; + Interop.OS.ThreadInfo threadInfo; + bool first = true; + + while (Interop.OS.GetNextThreadInfo(info.team, ref cookie, out threadInfo) == 0) + { + if (first) + { + procInfo.BasePriority = threadInfo.priority; + first = false; + } + + procInfo._threadInfoList.Add(new ThreadInfo() + { + _processId = threadInfo.team, + _threadId = (ulong)threadInfo.thread, + _basePriority = procInfo.BasePriority, + _currentPriority = threadInfo.priority, + _startAddress = null, + _threadState = GetThreadStateFromBThreadState(threadInfo.state), + _threadWaitReason = GetThreadWaitReasonFromBThreadState(threadInfo.state), + }); + } + } + + return procInfo; + } + + private static ThreadState GetThreadStateFromBThreadState(Interop.OS.ThreadState state) + { + switch (state) + { + case Interop.OS.ThreadState.B_THREAD_RUNNING: + return ThreadState.Running; + case Interop.OS.ThreadState.B_THREAD_READY: + return ThreadState.Ready; + case Interop.OS.ThreadState.B_THREAD_RECEIVING: + case Interop.OS.ThreadState.B_THREAD_ASLEEP: + case Interop.OS.ThreadState.B_THREAD_SUSPENDED: + case Interop.OS.ThreadState.B_THREAD_WAITING: + return ThreadState.Wait; + default: + return ThreadState.Unknown; + } + } + + private static ThreadWaitReason GetThreadWaitReasonFromBThreadState(Interop.OS.ThreadState state) + { + switch (state) + { + case Interop.OS.ThreadState.B_THREAD_ASLEEP: + return ThreadWaitReason.ExecutionDelay; + case Interop.OS.ThreadState.B_THREAD_SUSPENDED: + return ThreadWaitReason.Suspended; + default: + return ThreadWaitReason.Unknown; + } + } + + private static unsafe string GetString(byte* ptr, int maxLength) + { + int length = new ReadOnlySpan(ptr, maxLength).IndexOf((byte)0); + return new string((sbyte*)ptr, 0, (length < 0) ? maxLength : length); + } + } +} diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessThread.Haiku.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessThread.Haiku.cs new file mode 100644 index 00000000000000..1d22ccb7b51507 --- /dev/null +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessThread.Haiku.cs @@ -0,0 +1,129 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using System.Runtime.Versioning; + +namespace System.Diagnostics +{ + public partial class ProcessThread + { + /// + /// Returns or sets the priority level of the associated thread. The priority level is + /// not an absolute level, but instead contributes to the actual thread priority by + /// considering the priority class of the process. + /// + private ThreadPriorityLevel PriorityLevelCore + { + get + { + Interop.OS.ThreadInfo info; + int status = Interop.OS.GetThreadInfo(Id, out info); + + if (status != 0) + { + throw new Win32Exception(status); + } + + // thread_info.priority are BeOS-style priority values, not POSIX nice values (those returned by getpriority()). + return + (info.priority >= (int)Interop.OS.BPriority.B_REAL_TIME_DISPLAY_PRIORITY) ? ThreadPriorityLevel.TimeCritical : + (info.priority >= (int)Interop.OS.BPriority.B_URGENT_DISPLAY_PRIORITY) ? ThreadPriorityLevel.Highest : + (info.priority >= (int)Interop.OS.BPriority.B_DISPLAY_PRIORITY) ? ThreadPriorityLevel.AboveNormal : + (info.priority >= (int)Interop.OS.BPriority.B_NORMAL_PRIORITY) ? ThreadPriorityLevel.Normal : + (info.priority >= (int)Interop.OS.BPriority.B_LOW_PRIORITY) ? ThreadPriorityLevel.BelowNormal : + (info.priority >= (int)Interop.OS.BPriority.B_LOWEST_ACTIVE_PRIORITY) ? ThreadPriorityLevel.Lowest : + ThreadPriorityLevel.Idle; + } + set + { + int newPriority = (value == ThreadPriorityLevel.TimeCritical) ? (int)Interop.OS.BPriority.B_REAL_TIME_DISPLAY_PRIORITY : + (value == ThreadPriorityLevel.Highest) ? (int)Interop.OS.BPriority.B_URGENT_DISPLAY_PRIORITY : + (value == ThreadPriorityLevel.AboveNormal) ? (int)Interop.OS.BPriority.B_DISPLAY_PRIORITY : + (value == ThreadPriorityLevel.Normal) ? (int)Interop.OS.BPriority.B_NORMAL_PRIORITY : + (value == ThreadPriorityLevel.BelowNormal) ? (int)Interop.OS.BPriority.B_LOW_PRIORITY : + (value == ThreadPriorityLevel.Lowest) ? (int)Interop.OS.BPriority.B_LOWEST_ACTIVE_PRIORITY : + (int)Interop.OS.BPriority.B_IDLE_PRIORITY; + + int oldPriorityOrError = Interop.OS.SetThreadPriority(Id, newPriority); + + if (oldPriorityOrError < 0) + { + throw new Win32Exception(oldPriorityOrError); + } + } + } + + private static DateTime GetStartTime() => throw new PlatformNotSupportedException(); + + /// + /// Returns the amount of time the associated thread has spent utilizing the CPU. + /// It is the sum of the System.Diagnostics.ProcessThread.UserProcessorTime and + /// System.Diagnostics.ProcessThread.PrivilegedProcessorTime. + /// + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [SupportedOSPlatform("maccatalyst")] + public TimeSpan TotalProcessorTime + { + get + { + Interop.OS.ThreadInfo info; + int status = Interop.OS.GetThreadInfo(Id, out info); + + if (status != 0) + { + throw new Win32Exception(status); + } + + return TimeSpan.FromMicroseconds(info.user_time + info.kernel_time); + } + } + + /// + /// Returns the amount of time the associated thread has spent running code + /// inside the application (not the operating system core). + /// + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [SupportedOSPlatform("maccatalyst")] + public TimeSpan UserProcessorTime + { + get + { + Interop.OS.ThreadInfo info; + int status = Interop.OS.GetThreadInfo(Id, out info); + + if (status != 0) + { + throw new Win32Exception(status); + } + + return TimeSpan.FromMicroseconds(info.user_time); + } + } + + /// + /// Returns the amount of time the thread has spent running code inside the operating + /// system core. + /// + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [SupportedOSPlatform("maccatalyst")] + public TimeSpan PrivilegedProcessorTime + { + get + { + Interop.OS.ThreadInfo info; + int status = Interop.OS.GetThreadInfo(Id, out info); + + if (status != 0) + { + throw new Win32Exception(status); + } + + return TimeSpan.FromMicroseconds(info.kernel_time); + } + } + } +} diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessThread.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessThread.cs index aaf843466b0b41..6abd29c7e4d8ab 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessThread.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessThread.cs @@ -91,6 +91,7 @@ public ThreadPriorityLevel PriorityLevel [SupportedOSPlatform("windows")] [SupportedOSPlatform("linux")] [SupportedOSPlatform("freebsd")] + [SupportedOSPlatform("haiku")] get { if (!_priorityLevel.HasValue) @@ -100,6 +101,7 @@ public ThreadPriorityLevel PriorityLevel return _priorityLevel.Value; } [SupportedOSPlatform("windows")] + [SupportedOSPlatform("haiku")] set { PriorityLevelCore = value; diff --git a/src/native/libs/Common/pal_config.h.in b/src/native/libs/Common/pal_config.h.in index 58f883951f0397..f93b8d0816935d 100644 --- a/src/native/libs/Common/pal_config.h.in +++ b/src/native/libs/Common/pal_config.h.in @@ -140,6 +140,7 @@ #cmakedefine01 HAVE_NET_IF_H #cmakedefine01 HAVE_SYS_PROCINFO_H #cmakedefine01 HAVE_IOSS_H +#cmakedefine01 HAVE_IMAGE_H #cmakedefine01 HAVE_OS_H #cmakedefine01 HAVE_ALIGNED_ALLOC #cmakedefine01 HAVE_MALLOC_SIZE diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c index 34131dee0312e5..9edf40fa1ac9dc 100644 --- a/src/native/libs/System.Native/entrypoints.c +++ b/src/native/libs/System.Native/entrypoints.c @@ -307,6 +307,15 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_LowLevelCrossProcessMutex_SetAbandoned) DllImportEntry(SystemNative_Select) DllImportEntry(SystemNative_GetNextAreaInfo) + DllImportEntry(SystemNative_GetTeamInfo) + DllImportEntry(SystemNative_GetNextTeamInfo) + DllImportEntry(SystemNative_GetNextTeamId) + DllImportEntry(SystemNative_GetTeamUsageInfo) + DllImportEntry(SystemNative_SetThreadPriority) + DllImportEntry(SystemNative_GetThreadInfo) + DllImportEntry(SystemNative_GetNextThreadInfo) + DllImportEntry(SystemNative_GetSystemInfo) + DllImportEntry(SystemNative_GetNextImageInfo) }; EXTERN_C const void* SystemResolveDllImport(const char* name); diff --git a/src/native/libs/System.Native/pal_getosinfo.c b/src/native/libs/System.Native/pal_getosinfo.c index c499e9b9be68cb..13ad12b8d4f8e3 100644 --- a/src/native/libs/System.Native/pal_getosinfo.c +++ b/src/native/libs/System.Native/pal_getosinfo.c @@ -12,6 +12,39 @@ #include #endif +#if HAVE_IMAGE_H +#include +#endif + +#if HAVE_OS_H +static void CopyTeamInfo(TeamInfo* info, const team_info* nativeInfo) +{ + memset(info, 0, sizeof(*info)); + + info->team = nativeInfo->team; + info->session_id = nativeInfo->session_id; + info->parent = nativeInfo->parent; + SafeStringCopy((char*)info->name, sizeof(info->name), (const char*)nativeInfo->name); + info->start_time = nativeInfo->start_time; +} + +static void CopyThreadInfo(HaikuThreadInfo* info, const thread_info* nativeInfo) +{ + memset(info, 0, sizeof(*info)); + + info->thread = nativeInfo->thread; + info->team = nativeInfo->team; + info->state = nativeInfo->state; + info->priority = nativeInfo->priority; + info->user_time = nativeInfo->user_time; + info->kernel_time = nativeInfo->kernel_time; +} +#endif + +#if HAVE_IMAGE_H +c_static_assert(SYSTEMNATIVE_MAX_PATH >= MAXPATHLEN); +#endif + int32_t SystemNative_GetNextAreaInfo(int32_t team, intptr_t* cookie, AreaInfo* areaInfo) { #if HAVE_OS_H @@ -40,3 +73,231 @@ int32_t SystemNative_GetNextAreaInfo(int32_t team, intptr_t* cookie, AreaInfo* a return ENOTSUP; #endif } + +int32_t SystemNative_GetTeamInfo(int32_t team, TeamInfo* info) +{ +#if HAVE_OS_H + if (info == NULL) + { + return EINVAL; + } + + team_info nativeInfo; + memset(&nativeInfo, 0, sizeof(nativeInfo)); + + status_t status = get_team_info((team_id)team, &nativeInfo); + if (status != B_OK) + { + return (int32_t)status; + } + + CopyTeamInfo(info, &nativeInfo); + return (int32_t)status; +#else + (void)team; + (void)info; + return ENOTSUP; +#endif +} + +int32_t SystemNative_GetNextTeamInfo(int32_t* cookie, TeamInfo* info) +{ +#if HAVE_OS_H + if (cookie == NULL || info == NULL) + { + return EINVAL; + } + + team_info nativeInfo; + memset(&nativeInfo, 0, sizeof(nativeInfo)); + + status_t status = get_next_team_info(cookie, &nativeInfo); + if (status != B_OK) + { + return (int32_t)status; + } + + CopyTeamInfo(info, &nativeInfo); + return (int32_t)status; +#else + (void)cookie; + (void)info; + return ENOTSUP; +#endif +} + +int32_t SystemNative_GetNextTeamId(int32_t* cookie, int32_t* team) +{ +#if HAVE_OS_H + if (cookie == NULL || team == NULL) + { + return EINVAL; + } + + team_info nativeInfo; + memset(&nativeInfo, 0, sizeof(nativeInfo)); + + status_t status = get_next_team_info(cookie, &nativeInfo); + if (status != B_OK) + { + return (int32_t)status; + } + + *team = (int32_t)nativeInfo.team; + return (int32_t)status; +#else + (void)cookie; + (void)team; + return ENOTSUP; +#endif +} + +int32_t SystemNative_GetTeamUsageInfo(int32_t team, int32_t who, TeamUsageInfo* info) +{ +#if HAVE_OS_H + if (info == NULL) + { + return EINVAL; + } + + team_usage_info nativeInfo; + memset(&nativeInfo, 0, sizeof(nativeInfo)); + + status_t status = get_team_usage_info((team_id)team, who, &nativeInfo); + if (status != B_OK) + { + return (int32_t)status; + } + + info->user_time = nativeInfo.user_time; + info->kernel_time = nativeInfo.kernel_time; + return (int32_t)status; +#else + (void)team; + (void)who; + (void)info; + return ENOTSUP; +#endif +} + +int32_t SystemNative_SetThreadPriority(int32_t thread, int32_t newPriority) +{ +#if HAVE_OS_H + status_t status = set_thread_priority((thread_id)thread, newPriority); + return (int32_t)status; +#else + (void)thread; + (void)newPriority; + return ENOTSUP; +#endif +} + +int32_t SystemNative_GetThreadInfo(int32_t thread, HaikuThreadInfo* info) +{ +#if HAVE_OS_H + if (info == NULL) + { + return EINVAL; + } + + thread_info nativeInfo; + memset(&nativeInfo, 0, sizeof(nativeInfo)); + + status_t status = get_thread_info((thread_id)thread, &nativeInfo); + if (status != B_OK) + { + return (int32_t)status; + } + + CopyThreadInfo(info, &nativeInfo); + return (int32_t)status; +#else + (void)thread; + (void)info; + return ENOTSUP; +#endif +} + +int32_t SystemNative_GetNextThreadInfo(int32_t team, int32_t* cookie, HaikuThreadInfo* info) +{ +#if HAVE_OS_H + if (cookie == NULL || info == NULL) + { + return EINVAL; + } + + thread_info nativeInfo; + memset(&nativeInfo, 0, sizeof(nativeInfo)); + + status_t status = get_next_thread_info((team_id)team, cookie, &nativeInfo); + if (status != B_OK) + { + return (int32_t)status; + } + + CopyThreadInfo(info, &nativeInfo); + return (int32_t)status; +#else + (void)team; + (void)cookie; + (void)info; + return ENOTSUP; +#endif +} + +int32_t SystemNative_GetSystemInfo(SystemInfo* info) +{ +#if HAVE_OS_H + if (info == NULL) + { + return EINVAL; + } + + system_info nativeInfo; + memset(&nativeInfo, 0, sizeof(nativeInfo)); + + status_t status = get_system_info(&nativeInfo); + if (status != B_OK) + { + return (int32_t)status; + } + + info->boot_time = nativeInfo.boot_time; + return (int32_t)status; +#else + (void)info; + return ENOTSUP; +#endif +} + +int32_t SystemNative_GetNextImageInfo(int32_t team, int32_t* cookie, ImageInfo* info) +{ +#if HAVE_OS_H && HAVE_IMAGE_H + if (cookie == NULL || info == NULL) + { + return EINVAL; + } + + image_info nativeInfo; + memset(&nativeInfo, 0, sizeof(nativeInfo)); + + status_t status = get_next_image_info((team_id)team, cookie, &nativeInfo); + if (status != B_OK) + { + return (int32_t)status; + } + + memset(info, 0, sizeof(*info)); + info->type = nativeInfo.type; + SafeStringCopy((char*)info->name, sizeof(info->name), (const char*)nativeInfo.name); + info->text = (uintptr_t)nativeInfo.text; + info->text_size = nativeInfo.text_size; + info->data_size = nativeInfo.data_size; + return (int32_t)status; +#else + (void)team; + (void)cookie; + (void)info; + return ENOTSUP; +#endif +} diff --git a/src/native/libs/System.Native/pal_getosinfo.h b/src/native/libs/System.Native/pal_getosinfo.h index 464269e1054347..1ab43944eb1333 100644 --- a/src/native/libs/System.Native/pal_getosinfo.h +++ b/src/native/libs/System.Native/pal_getosinfo.h @@ -6,10 +6,61 @@ #include "pal_compiler.h" #include "pal_types.h" +#define SYSTEMNATIVE_OS_NAME_LENGTH 32 +#define SYSTEMNATIVE_MAX_PATH 1024 + typedef struct { uintptr_t size; uint32_t ram_size; } AreaInfo; +typedef struct +{ + int32_t team; + int32_t session_id; + int32_t parent; + uint8_t name[SYSTEMNATIVE_OS_NAME_LENGTH]; + int64_t start_time; +} TeamInfo; + +typedef struct +{ + int64_t user_time; + int64_t kernel_time; +} TeamUsageInfo; + +typedef struct +{ + int32_t thread; + int32_t team; + int32_t state; + int32_t priority; + int64_t user_time; + int64_t kernel_time; +} HaikuThreadInfo; + +typedef struct +{ + int64_t boot_time; +} SystemInfo; + +typedef struct +{ + int32_t type; + uint8_t name[SYSTEMNATIVE_MAX_PATH]; + uintptr_t text; + int32_t text_size; + int32_t data_size; +} ImageInfo; + PALEXPORT int32_t SystemNative_GetNextAreaInfo(int32_t team, intptr_t* cookie, AreaInfo* areaInfo); +PALEXPORT int32_t SystemNative_GetTeamInfo(int32_t team, TeamInfo* info); +PALEXPORT int32_t SystemNative_GetNextTeamInfo(int32_t* cookie, TeamInfo* info); +PALEXPORT int32_t SystemNative_GetNextTeamId(int32_t* cookie, int32_t* team); +PALEXPORT int32_t SystemNative_GetTeamUsageInfo(int32_t team, int32_t who, TeamUsageInfo* info); +PALEXPORT int32_t SystemNative_SetThreadPriority(int32_t thread, int32_t newPriority); +PALEXPORT int32_t SystemNative_GetThreadInfo(int32_t thread, HaikuThreadInfo* info); +PALEXPORT int32_t SystemNative_GetNextThreadInfo(int32_t team, int32_t* cookie, HaikuThreadInfo* info); +PALEXPORT int32_t SystemNative_GetSystemInfo(SystemInfo* info); +PALEXPORT int32_t SystemNative_GetNextImageInfo(int32_t team, int32_t* cookie, ImageInfo* info); diff --git a/src/native/libs/configure.cmake b/src/native/libs/configure.cmake index ba504df4834918..4d2e3cb81bf505 100644 --- a/src/native/libs/configure.cmake +++ b/src/native/libs/configure.cmake @@ -997,6 +997,10 @@ check_include_files( IOKit/serial/ioss.h HAVE_IOSS_H) +check_include_files( + image.h + HAVE_IMAGE_H) + check_include_files( OS.h HAVE_OS_H) From 8f9bb5b3ef695bbe72df3f7119f8b3645ef73c17 Mon Sep 17 00:00:00 2001 From: Trung Nguyen <57174311+trungnt2910@users.noreply.github.com> Date: Sat, 22 Nov 2025 03:49:02 +1100 Subject: [PATCH 2/4] Haiku: Declare as supported platform Declare Haiku as a supported platform to MSBuild for all `System.Diagnostics.Process` builds. This prevents `CA1418` when using the `SupportedOSPlatform` attribute with `haiku`. --- .../src/System.Diagnostics.Process.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj index e924bcecff2ddb..51fca4bcbbf052 100644 --- a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj +++ b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj @@ -419,6 +419,10 @@ + + + + From d4609103ddaee7dfb11db18aec9ab0fb57247162 Mon Sep 17 00:00:00 2001 From: Trung Nguyen <57174311+trungnt2910@users.noreply.github.com> Date: Sat, 22 Nov 2025 12:50:45 +1100 Subject: [PATCH 3/4] Haiku: Align attributes with reference API defs --- .../ref/System.Diagnostics.Process.cs | 2 +- .../src/System/Diagnostics/Process.cs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs b/src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs index 0907c2dc7b69f1..06bd962d02b7a2 100644 --- a/src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs +++ b/src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs @@ -369,7 +369,7 @@ internal ProcessThread() { } public int Id { get { throw null; } } public int IdealProcessor { set { } } public bool PriorityBoostEnabled { get { throw null; } set { } } - public System.Diagnostics.ThreadPriorityLevel PriorityLevel { [System.Runtime.Versioning.SupportedOSPlatform("windows")] [System.Runtime.Versioning.SupportedOSPlatform("linux")] [System.Runtime.Versioning.SupportedOSPlatform("freebsd")] get { throw null; } [System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")] set { } } + public System.Diagnostics.ThreadPriorityLevel PriorityLevel { [System.Runtime.Versioning.SupportedOSPlatform("windows")] [System.Runtime.Versioning.SupportedOSPlatform("linux")] [System.Runtime.Versioning.SupportedOSPlatform("freebsd")] [System.Runtime.Versioning.SupportedOSPlatform("haiku")] get { throw null; } [System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")] [System.Runtime.Versioning.SupportedOSPlatformAttribute("haiku")] set { } } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")] [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")] [System.Runtime.Versioning.SupportedOSPlatformAttribute("maccatalyst")] // this needs to come after the ios attribute due to limitations in the platform analyzer diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs index 7ed47855d7ee51..8966e578389e92 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs @@ -276,7 +276,6 @@ public IntPtr MaxWorkingSet [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("tvos")] [SupportedOSPlatform("maccatalyst")] - [UnsupportedOSPlatform("haiku")] get { EnsureWorkingSetLimits(); @@ -301,7 +300,6 @@ public IntPtr MinWorkingSet [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("tvos")] [SupportedOSPlatform("maccatalyst")] - [UnsupportedOSPlatform("haiku")] get { EnsureWorkingSetLimits(); From 0b80a9e38506d4f0915adadda63ae6e315a229cb Mon Sep 17 00:00:00 2001 From: Trung Nguyen <57174311+trungnt2910@users.noreply.github.com> Date: Sun, 26 Apr 2026 23:18:28 +1000 Subject: [PATCH 4/4] Haiku: Add missing enum-to-int casts Otherwise `clang`-based builds will complain. `clang` treats the `enum`s as unsigned `int`, triggering a sign conversion warning. --- src/native/libs/System.Native/pal_getosinfo.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/native/libs/System.Native/pal_getosinfo.c b/src/native/libs/System.Native/pal_getosinfo.c index 13ad12b8d4f8e3..98806b3ddc4691 100644 --- a/src/native/libs/System.Native/pal_getosinfo.c +++ b/src/native/libs/System.Native/pal_getosinfo.c @@ -34,7 +34,7 @@ static void CopyThreadInfo(HaikuThreadInfo* info, const thread_info* nativeInfo) info->thread = nativeInfo->thread; info->team = nativeInfo->team; - info->state = nativeInfo->state; + info->state = (int32_t)nativeInfo->state; info->priority = nativeInfo->priority; info->user_time = nativeInfo->user_time; info->kernel_time = nativeInfo->kernel_time; @@ -288,7 +288,7 @@ int32_t SystemNative_GetNextImageInfo(int32_t team, int32_t* cookie, ImageInfo* } memset(info, 0, sizeof(*info)); - info->type = nativeInfo.type; + info->type = (int32_t)nativeInfo.type; SafeStringCopy((char*)info->name, sizeof(info->name), (const char*)nativeInfo.name); info->text = (uintptr_t)nativeInfo.text; info->text_size = nativeInfo.text_size;