diff --git a/src/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs b/src/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs
index c41415a7ec65..66637a3a1457 100644
--- a/src/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs
+++ b/src/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs
@@ -15,12 +15,14 @@ internal static partial class procfs
{
internal const string RootPath = "/proc/";
private const string ExeFileName = "/exe";
+ private const string CmdLineFileName = "/cmdline";
private const string StatFileName = "/stat";
private const string MapsFileName = "/maps";
private const string FileDescriptorDirectoryName = "/fd/";
private const string TaskDirectoryName = "/task/";
internal const string SelfExeFilePath = RootPath + "self" + ExeFileName;
+ internal const string SelfCmdLineFilePath = RootPath + "self" + CmdLineFileName;
internal const string ProcStatFilePath = RootPath + "stat";
internal struct ParsedStat
@@ -31,7 +33,7 @@ internal struct ParsedStat
// the MoveNext() with the appropriate ParseNext* call and assignment.
internal int pid;
- internal string comm;
+ // internal string comm;
internal char state;
internal int ppid;
//internal int pgrp;
@@ -87,6 +89,11 @@ internal static string GetExeFilePathForProcess(int pid)
return RootPath + pid.ToString(CultureInfo.InvariantCulture) + ExeFileName;
}
+ internal static string GetCmdLinePathForProcess(int pid)
+ {
+ return RootPath + pid.ToString(CultureInfo.InvariantCulture) + CmdLineFileName;
+ }
+
internal static string GetStatFilePathForProcess(int pid)
{
return RootPath + pid.ToString(CultureInfo.InvariantCulture) + StatFileName;
@@ -231,7 +238,7 @@ internal static bool TryParseStatFile(string statFilePath, out ParsedStat result
var results = default(ParsedStat);
results.pid = parser.ParseNextInt32();
- results.comm = parser.MoveAndExtractNextInOuterParens();
+ parser.MoveAndExtractNextInOuterParens(extractValue: false); // comm
results.state = parser.ParseNextChar();
results.ppid = parser.ParseNextInt32();
parser.MoveNextOrFail(); // pgrp
diff --git a/src/Common/src/System/IO/StringParser.cs b/src/Common/src/System/IO/StringParser.cs
index d79d7ce2e82e..b94a7000d876 100644
--- a/src/Common/src/System/IO/StringParser.cs
+++ b/src/Common/src/System/IO/StringParser.cs
@@ -98,7 +98,7 @@ public string MoveAndExtractNext()
/// in the string. The extracted value will be everything between (not including) those parentheses.
///
///
- public string MoveAndExtractNextInOuterParens()
+ public string MoveAndExtractNextInOuterParens(bool extractValue = true)
{
// Move to the next position
MoveNextOrFail();
@@ -118,7 +118,7 @@ public string MoveAndExtractNextInOuterParens()
}
// Extract the contents of the parens, then move our ending position to be after the paren
- string result = _buffer.Substring(_startIndex + 1, lastParen - _startIndex - 1);
+ string result = extractValue ? _buffer.Substring(_startIndex + 1, lastParen - _startIndex - 1) : null;
_endIndex = lastParen + 1;
return result;
diff --git a/src/Common/tests/Tests/Interop/procfsTests.cs b/src/Common/tests/Tests/Interop/procfsTests.cs
index 53aa6ae8d421..7fd7647410fd 100644
--- a/src/Common/tests/Tests/Interop/procfsTests.cs
+++ b/src/Common/tests/Tests/Interop/procfsTests.cs
@@ -49,7 +49,6 @@ public static void ParseValidStatFiles_Success(
Assert.True(Interop.procfs.TryParseStatFile(path, out result, new ReusableTextReader()));
Assert.Equal(expectedPid, result.pid);
- Assert.Equal(expectedComm, result.comm);
Assert.Equal(expectedState, result.state);
Assert.Equal(expectedSession, result.session);
Assert.Equal(expectedUtime, result.utime);
diff --git a/src/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs b/src/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs
index eca3afd10600..013418acf92d 100644
--- a/src/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs
+++ b/src/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Buffers;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
@@ -29,11 +30,9 @@ public static Process[] GetProcessesByName(string processName, string machineNam
var processes = new List();
foreach (int pid in ProcessManager.EnumerateProcessIds())
{
- Interop.procfs.ParsedStat parsedStat;
- if (Interop.procfs.TryReadStatFile(pid, out parsedStat, reusableReader) &&
- string.Equals(processName, parsedStat.comm, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(processName, Process.GetProcessName(pid), StringComparison.OrdinalIgnoreCase))
{
- ProcessInfo processInfo = ProcessManager.CreateProcessInfo(parsedStat, reusableReader);
+ ProcessInfo processInfo = ProcessManager.CreateProcessInfo(pid, reusableReader, processName);
processes.Add(new Process(machineName, false, processInfo.ProcessId, processInfo));
}
}
@@ -256,6 +255,74 @@ internal static string GetExePath(int processId = -1)
return Interop.Sys.ReadLink(exeFilePath);
}
+ /// Gets the name that was used to start the process, or null if it could not be retrieved.
+ /// The pid for the target process, or -1 for the current process.
+ internal static string GetProcessName(int processId = -1)
+ {
+ string cmdLineFilePath = processId == -1 ?
+ Interop.procfs.SelfCmdLineFilePath :
+ Interop.procfs.GetCmdLinePathForProcess(processId);
+
+ byte[] rentedArray = null;
+ try
+ {
+ // bufferSize == 1 used to avoid unnecessary buffer in FileStream
+ using (var fs = new FileStream(cmdLineFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 1, useAsync: false))
+ {
+ Span buffer = stackalloc byte[512];
+ int bytesRead = 0;
+ while (true)
+ {
+ // Resize buffer if it was too small.
+ if (bytesRead == buffer.Length)
+ {
+ uint newLength = (uint)buffer.Length * 2;
+
+ byte[] tmp = ArrayPool.Shared.Rent((int)newLength);
+ buffer.CopyTo(tmp);
+ byte[] toReturn = rentedArray;
+ buffer = rentedArray = tmp;
+ if (rentedArray != null)
+ {
+ ArrayPool.Shared.Return(toReturn);
+ }
+ }
+
+ Debug.Assert(bytesRead < buffer.Length);
+ int n = fs.Read(buffer.Slice(bytesRead));
+ bytesRead += n;
+
+ // cmdline contains the argv array separated by '\0' bytes.
+ // we determine the process name using argv[0].
+ int argv0End = buffer.Slice(0, bytesRead).IndexOf((byte)'\0');
+ if (argv0End != -1)
+ {
+ // Strip directory names from argv[0].
+ int nameStart = buffer.Slice(0, argv0End).LastIndexOf((byte)'/') + 1;
+
+ return Encoding.UTF8.GetString(buffer.Slice(nameStart, argv0End - nameStart));
+ }
+
+ if (n == 0)
+ {
+ return null;
+ }
+ }
+ }
+ }
+ catch (IOException)
+ {
+ return null;
+ }
+ finally
+ {
+ if (rentedArray != null)
+ {
+ ArrayPool.Shared.Return(rentedArray);
+ }
+ }
+ }
+
// ----------------------------------
// ---- Unix PAL layer ends here ----
// ----------------------------------
diff --git a/src/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Linux.cs b/src/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Linux.cs
index 69800f10b0fb..53e2c4586f97 100644
--- a/src/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Linux.cs
+++ b/src/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Linux.cs
@@ -108,7 +108,7 @@ internal static ProcessModuleCollection GetModules(int processId)
///
/// Creates a ProcessInfo from the specified process ID.
///
- internal static ProcessInfo CreateProcessInfo(int pid, ReusableTextReader reusableReader = null)
+ internal static ProcessInfo CreateProcessInfo(int pid, ReusableTextReader reusableReader = null, string processName = null)
{
if (reusableReader == null)
{
@@ -117,21 +117,21 @@ internal static ProcessInfo CreateProcessInfo(int pid, ReusableTextReader reusab
Interop.procfs.ParsedStat stat;
return Interop.procfs.TryReadStatFile(pid, out stat, reusableReader) ?
- CreateProcessInfo(stat, reusableReader) :
+ CreateProcessInfo(stat, reusableReader, processName) :
null;
}
///
/// Creates a ProcessInfo from the data parsed from a /proc/pid/stat file and the associated tasks directory.
///
- internal static ProcessInfo CreateProcessInfo(Interop.procfs.ParsedStat procFsStat, ReusableTextReader reusableReader)
+ internal static ProcessInfo CreateProcessInfo(Interop.procfs.ParsedStat procFsStat, ReusableTextReader reusableReader, string processName)
{
int pid = procFsStat.pid;
var pi = new ProcessInfo()
{
ProcessId = pid,
- ProcessName = procFsStat.comm,
+ ProcessName = processName ?? Process.GetProcessName(pid) ?? string.Empty,
BasePriority = (int)procFsStat.nice,
VirtualBytes = (long)procFsStat.vsize,
WorkingSet = procFsStat.rss * Environment.SystemPageSize,
diff --git a/src/System.Diagnostics.Process/tests/ProcessTestBase.cs b/src/System.Diagnostics.Process/tests/ProcessTestBase.cs
index 8fc68010c392..9b15ab99324e 100644
--- a/src/System.Diagnostics.Process/tests/ProcessTestBase.cs
+++ b/src/System.Diagnostics.Process/tests/ProcessTestBase.cs
@@ -100,6 +100,16 @@ protected void StartSleepKillWait(Process p)
///
///
protected static bool IsProgramInstalled(string program)
+ {
+ return GetProgramPath(program) != null;
+ }
+
+ ///
+ /// Return program path
+ ///
+ ///
+ ///
+ protected static string GetProgramPath(string program)
{
string path;
string pathEnvVar = Environment.GetEnvironmentVariable("PATH");
@@ -113,11 +123,11 @@ protected static bool IsProgramInstalled(string program)
path = Path.Combine(subPath, program);
if (File.Exists(path))
{
- return true;
+ return path;
}
}
}
- return false;
+ return null;
}
}
}
diff --git a/src/System.Diagnostics.Process/tests/ProcessTests.cs b/src/System.Diagnostics.Process/tests/ProcessTests.cs
index fe0897f4129b..191830beb5c2 100644
--- a/src/System.Diagnostics.Process/tests/ProcessTests.cs
+++ b/src/System.Diagnostics.Process/tests/ProcessTests.cs
@@ -1875,6 +1875,37 @@ public void TestLongProcessIsWorking()
Assert.True(p.HasExited);
}
+ [PlatformSpecific(TestPlatforms.AnyUnix)]
+ [ActiveIssue(37054, TestPlatforms.OSX)]
+ [Fact]
+ public void LongProcessNamesAreSupported()
+ {
+ string programPath = GetProgramPath("sleep");
+
+ if (programPath == null)
+ {
+ return;
+ }
+
+ const string LongProcessName = "123456789012345678901234567890";
+ string sleepCommandPathFileName = Path.Combine(TestDirectory, LongProcessName);
+ File.Copy(programPath, sleepCommandPathFileName);
+
+ using (Process px = Process.Start(sleepCommandPathFileName, "600"))
+ {
+ Process[] runningProcesses = Process.GetProcesses();
+ try
+ {
+ Assert.Contains(runningProcesses, p => p.ProcessName == LongProcessName);
+ }
+ finally
+ {
+ px.Kill();
+ px.WaitForExit();
+ }
+ }
+ }
+
private string GetCurrentProcessName()
{
return $"{Process.GetCurrentProcess().ProcessName}.exe";