Skip to content

Commit 5ba1826

Browse files
committed
SunOS process and thread support
Read binary psinfo for System.Diagnostic.Process on SunOS Thanks for initial prototype help from: Austin Wise <[email protected]> Add Try prefix to SunOS Interop functions Get rid of unsafe for procfs get methods
1 parent 28f729b commit 5ba1826

17 files changed

+846
-93
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
// C# equivalents for <sys/procfs.h> structures. See: struct lwpsinfo, struct psinfo.
8+
// We read directly onto these from procfs, so the layouts and sizes of these structures
9+
// must _exactly_ match those in <sys/procfs.h>
10+
11+
// analyzer incorrectly flags fixed buffer length const
12+
// (https://github.com/dotnet/roslyn/issues/37593)
13+
#pragma warning disable CA1823
14+
15+
internal static partial class Interop
16+
{
17+
internal static partial class @procfs
18+
{
19+
internal const string RootPath = "/proc/";
20+
private const string psinfoFileName = "/psinfo";
21+
private const string lwpDirName = "/lwp";
22+
private const string lwpsinfoFileName = "/lwpsinfo";
23+
24+
// Constants from sys/procfs.h
25+
private const int PRARGSZ = 80;
26+
private const int PRCLSZ = 8;
27+
private const int PRFNSZ = 16;
28+
29+
[StructLayout(LayoutKind.Sequential)]
30+
internal struct @timestruc_t
31+
{
32+
public long tv_sec;
33+
public long tv_nsec;
34+
}
35+
36+
// lwp ps(1) information file. /proc/<pid>/lwp/<lwpid>/lwpsinfo
37+
// Equivalent to sys/procfs.h struct lwpsinfo
38+
// "unsafe" because it has fixed sized arrays.
39+
[StructLayout(LayoutKind.Sequential)]
40+
internal unsafe struct @lwpsinfo
41+
{
42+
private int pr_flag; /* lwp flags (DEPRECATED; do not use) */
43+
public uint pr_lwpid; /* lwp id */
44+
private long pr_addr; /* internal address of lwp */
45+
private long pr_wchan; /* wait addr for sleeping lwp */
46+
public byte pr_stype; /* synchronization event type */
47+
public byte pr_state; /* numeric lwp state */
48+
public byte pr_sname; /* printable character for pr_state */
49+
public byte pr_nice; /* nice for cpu usage */
50+
private short pr_syscall; /* system call number (if in syscall) */
51+
private byte pr_oldpri; /* pre-SVR4, low value is high priority */
52+
private byte pr_cpu; /* pre-SVR4, cpu usage for scheduling */
53+
public int pr_pri; /* priority, high value is high priority */
54+
private ushort pr_pctcpu; /* fixed pt. % of recent cpu time */
55+
private ushort pr_pad;
56+
public timestruc_t pr_start; /* lwp start time, from the epoch */
57+
public timestruc_t pr_time; /* usr+sys cpu time for this lwp */
58+
private fixed byte pr_clname[PRCLSZ]; /* scheduling class name */
59+
private fixed byte pr_name[PRFNSZ]; /* name of system lwp */
60+
private int pr_onpro; /* processor which last ran this lwp */
61+
private int pr_bindpro; /* processor to which lwp is bound */
62+
private int pr_bindpset; /* processor set to which lwp is bound */
63+
private int pr_lgrp; /* lwp home lgroup */
64+
private fixed int pr_filler[4]; /* reserved for future use */
65+
}
66+
private const int PR_LWPSINFO_SIZE = 128; // for debug assertions
67+
68+
// process ps(1) information file. /proc/<pid>/psinfo
69+
// Equivalent to sys/procfs.h struct psinfo
70+
// "unsafe" because it has fixed sized arrays.
71+
[StructLayout(LayoutKind.Sequential)]
72+
internal unsafe struct @psinfo
73+
{
74+
private int pr_flag; /* process flags (DEPRECATED; do not use) */
75+
public int pr_nlwp; /* number of active lwps in the process */
76+
public int pr_pid; /* unique process id */
77+
public int pr_ppid; /* process id of parent */
78+
public int pr_pgid; /* pid of process group leader */
79+
public int pr_sid; /* session id */
80+
public uint pr_uid; /* real user id */
81+
public uint pr_euid; /* effective user id */
82+
public uint pr_gid; /* real group id */
83+
public uint pr_egid; /* effective group id */
84+
private long pr_addr; /* address of process */
85+
public ulong pr_size; /* size of process image in Kbytes */
86+
public ulong pr_rssize; /* resident set size in Kbytes */
87+
private ulong pr_pad1;
88+
private ulong pr_ttydev; /* controlling tty device (or PRNODEV) */
89+
private ushort pr_pctcpu; /* % of recent cpu time used by all lwps */
90+
private ushort pr_pctmem; /* % of system memory used by process */
91+
public timestruc_t pr_start; /* process start time, from the epoch */
92+
public timestruc_t pr_time; /* usr+sys cpu time for this process */
93+
public timestruc_t pr_ctime; /* usr+sys cpu time for reaped children */
94+
public fixed byte pr_fname[PRFNSZ]; /* name of execed file */
95+
public fixed byte pr_psargs[PRARGSZ]; /* initial characters of arg list */
96+
public int pr_wstat; /* if zombie, the wait() status */
97+
public int pr_argc; /* initial argument count */
98+
private long pr_argv; /* address of initial argument vector */
99+
private long pr_envp; /* address of initial environment vector */
100+
private byte pr_dmodel; /* data model of the process */
101+
private fixed byte pr_pad2[3];
102+
public int pr_taskid; /* task id */
103+
public int pr_projid; /* project id */
104+
public int pr_nzomb; /* number of zombie lwps in the process */
105+
public int pr_poolid; /* pool id */
106+
public int pr_zoneid; /* zone id */
107+
public int pr_contract; /* process contract */
108+
private fixed int pr_filler[1]; /* reserved for future use */
109+
public lwpsinfo pr_lwp; /* information for representative lwp */
110+
// C# magic: Accessor method to get a Span for pr_psargs[]
111+
// Does not affect the size or layout of this struct.
112+
internal ReadOnlySpan<byte> PsArgsSpan =>
113+
MemoryMarshal.CreateReadOnlySpan(ref pr_psargs[0], PRARGSZ);
114+
}
115+
private const int PR_PSINFO_SIZE = 416; // for debug assertions
116+
117+
// Ouput type for TryGetThreadInfoById()
118+
internal struct ThreadInfo
119+
{
120+
internal uint Tid;
121+
internal int Priority;
122+
internal int NiceVal;
123+
internal char Status;
124+
internal Interop.Sys.TimeSpec StartTime;
125+
internal Interop.Sys.TimeSpec CpuTotalTime; // user+sys
126+
// add more fields when needed.
127+
}
128+
129+
// Ouput type for TryGetProcessInfoById()
130+
internal struct ProcessInfo
131+
{
132+
internal int Pid;
133+
internal int ParentPid;
134+
internal int SessionId;
135+
internal int Priority;
136+
internal int NiceVal;
137+
internal nuint VirtualSize;
138+
internal nuint ResidentSetSize;
139+
internal Interop.Sys.TimeSpec StartTime;
140+
internal Interop.Sys.TimeSpec CpuTotalTime; // user+sys
141+
internal string? Args;
142+
// add more fields when needed.
143+
}
144+
145+
internal static string GetInfoFilePathForProcess(int pid) =>
146+
$"{RootPath}{(uint)pid}{psinfoFileName}";
147+
148+
internal static string GetLwpDirForProcess(int pid) =>
149+
$"{RootPath}{(uint)pid}{lwpDirName}";
150+
151+
internal static string GetInfoFilePathForThread(int pid, int tid) =>
152+
$"{RootPath}{(uint)pid}{lwpDirName}/{(uint)tid}{lwpsinfoFileName}";
153+
154+
}
155+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.Runtime.CompilerServices;
8+
using System.Runtime.InteropServices;
9+
using System.IO;
10+
using System.Text;
11+
12+
internal static partial class Interop
13+
{
14+
internal static partial class @procfs
15+
{
16+
17+
/// <summary>
18+
/// Attempts to get status info for the specified process ID.
19+
/// </summary>
20+
/// <param name="pid">PID of the process to read status info for.</param>
21+
/// <param name="result">The pointer to ProcessInfo instance.</param>
22+
/// <returns>
23+
/// true if the process info was read; otherwise, false.
24+
/// </returns>
25+
26+
// ProcessManager.SunOS.cs calls this
27+
internal static bool TryGetProcessInfoById(int pid, out ProcessInfo result)
28+
{
29+
result = default;
30+
31+
try
32+
{
33+
string fileName = GetInfoFilePathForProcess(pid);
34+
using FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
35+
psinfo pr;
36+
Unsafe.SkipInit(out pr);
37+
Span<byte> prspan = MemoryMarshal.AsBytes(new Span<psinfo>(ref pr));
38+
Debug.Assert(prspan.Length == PR_PSINFO_SIZE,
39+
$"psinfo struct size {prspan.Length} bytes not {PR_PSINFO_SIZE}.");
40+
fs.ReadExactly(prspan);
41+
42+
result.Pid = pr.pr_pid;
43+
result.ParentPid = pr.pr_ppid;
44+
result.SessionId = pr.pr_sid;
45+
result.VirtualSize = (nuint)pr.pr_size * 1024; // pr_size is in Kbytes
46+
result.ResidentSetSize = (nuint)pr.pr_rssize * 1024; // pr_rssize is in Kbytes
47+
result.StartTime.TvSec = pr.pr_start.tv_sec;
48+
result.StartTime.TvNsec = pr.pr_start.tv_nsec;
49+
result.CpuTotalTime.TvSec = pr.pr_time.tv_sec;
50+
result.CpuTotalTime.TvNsec = pr.pr_time.tv_nsec;
51+
52+
// Get Args as a managed string, using accessor for pr_psargs[]
53+
ReadOnlySpan<byte> argspan = pr.PsArgsSpan;
54+
int argslen = argspan.IndexOf((byte)0);
55+
argslen = (argslen >= 0) ? argslen : argspan.Length;
56+
result.Args = Encoding.UTF8.GetString(argspan.Slice(0, argslen));
57+
58+
// A couple things from pr_lwp
59+
result.Priority = pr.pr_lwp.pr_pri;
60+
result.NiceVal = (int)pr.pr_lwp.pr_nice;
61+
62+
return true;
63+
}
64+
catch (Exception e)
65+
{
66+
Debug.Fail($"Failed to read process info for PID {pid}: {e}");
67+
}
68+
69+
return false;
70+
}
71+
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.Runtime.CompilerServices;
8+
using System.Runtime.InteropServices;
9+
using System.IO;
10+
11+
internal static partial class Interop
12+
{
13+
internal static partial class @procfs
14+
{
15+
16+
/// <summary>
17+
/// Attempts to get status info for the specified thread ID.
18+
/// </summary>
19+
/// <param name="pid">PID of the process to read status info for.</param>
20+
/// <param name="tid">TID of the thread to read status info for.</param>
21+
/// <param name="result">The pointer to ThreadInfo instance.</param>
22+
/// <returns>
23+
/// true if the thread info was read; otherwise, false.
24+
/// </returns>
25+
26+
// ProcessManager.SunOS.cs calls this
27+
internal static bool TryGetThreadInfoById(int pid, int tid, out ThreadInfo result)
28+
{
29+
result = default;
30+
31+
try
32+
{
33+
string fileName = GetInfoFilePathForThread(pid, tid);
34+
using FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
35+
lwpsinfo pr;
36+
Unsafe.SkipInit(out pr);
37+
Span<byte> prspan = MemoryMarshal.AsBytes(new Span<lwpsinfo>(ref pr));
38+
Debug.Assert(prspan.Length == PR_LWPSINFO_SIZE,
39+
$"psinfo struct size {prspan.Length} bytes not {PR_LWPSINFO_SIZE}.");
40+
fs.ReadExactly(prspan);
41+
42+
result.Tid = pr.pr_lwpid;
43+
result.Priority = pr.pr_pri;
44+
result.NiceVal = (int)pr.pr_nice;
45+
result.Status = (char)pr.pr_sname;
46+
result.StartTime.TvSec = pr.pr_start.tv_sec;
47+
result.StartTime.TvNsec = pr.pr_start.tv_nsec;
48+
result.CpuTotalTime.TvSec = pr.pr_time.tv_sec;
49+
result.CpuTotalTime.TvNsec = pr.pr_time.tv_nsec;
50+
51+
return true;
52+
}
53+
catch (Exception e)
54+
{
55+
Debug.Fail($"Failed to read thread info for PID {pid} TID {tid}: {e}");
56+
}
57+
58+
return false;
59+
}
60+
61+
}
62+
}

src/libraries/Common/src/Interop/SunOS/procfs/Interop.ProcFsStat.TryReadProcessStatusInfo.cs

-37
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
internal static partial class Interop
8+
{
9+
internal static partial class Sys
10+
{
11+
internal struct TimeSpec
12+
{
13+
internal long TvSec;
14+
internal long TvNsec;
15+
}
16+
}
17+
}

src/libraries/Common/src/Interop/Unix/System.Native/Interop.UTimensat.cs

-6
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,6 @@ internal static partial class Interop
88
{
99
internal static partial class Sys
1010
{
11-
internal struct TimeSpec
12-
{
13-
internal long TvSec;
14-
internal long TvNsec;
15-
}
16-
1711
/// <summary>
1812
/// Sets the last access and last modified time of a file
1913
/// </summary>

src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public static partial class PlatformDetection
4747
public static bool IsNotMacCatalyst => !IsMacCatalyst;
4848
public static bool Isillumos => RuntimeInformation.IsOSPlatform(OSPlatform.Create("ILLUMOS"));
4949
public static bool IsSolaris => RuntimeInformation.IsOSPlatform(OSPlatform.Create("SOLARIS"));
50+
public static bool IsSunOS => Isillumos || IsSolaris;
5051
public static bool IsBrowser => RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"));
5152
public static bool IsWasi => RuntimeInformation.IsOSPlatform(OSPlatform.Create("WASI"));
5253
public static bool IsNotBrowser => !IsBrowser;

0 commit comments

Comments
 (0)