Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/Common/src/System/IO/StringParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public string MoveAndExtractNext()
/// in the string. The extracted value will be everything between (not including) those parentheses.
/// </summary>
/// <returns></returns>
public string MoveAndExtractNextInOuterParens()
public string MoveAndExtractNextInOuterParens(bool extractValue = true)
{
// Move to the next position
MoveNextOrFail();
Expand All @@ -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;
Expand Down
1 change: 0 additions & 1 deletion src/Common/tests/Tests/Interop/procfsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,11 +30,9 @@ public static Process[] GetProcessesByName(string processName, string machineNam
var processes = new List<Process>();
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));
}
}
Expand Down Expand Up @@ -256,6 +255,73 @@ internal static string GetExePath(int processId = -1)
return Interop.Sys.ReadLink(exeFilePath);
}

/// <summary>Gets the name that was used to start the process, or null if it could not be retrieved.</summary>
/// <param name="processId">The pid for the target process, or -1 for the current process.</param>
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<byte> 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<byte>.Shared.Rent((int)newLength);
buffer.CopyTo(tmp);
if (rentedArray != null)
{
ArrayPool<byte>.Shared.Return(rentedArray);
}
buffer = rentedArray = tmp;
}

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<byte>.Shared.Return(rentedArray);
}
}
}

// ----------------------------------
// ---- Unix PAL layer ends here ----
// ----------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ internal static ProcessModuleCollection GetModules(int processId)
/// <summary>
/// Creates a ProcessInfo from the specified process ID.
/// </summary>
internal static ProcessInfo CreateProcessInfo(int pid, ReusableTextReader reusableReader = null)
internal static ProcessInfo CreateProcessInfo(int pid, ReusableTextReader reusableReader = null, string processName = null)
{
if (reusableReader == null)
{
Expand All @@ -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;
}

/// <summary>
/// Creates a ProcessInfo from the data parsed from a /proc/pid/stat file and the associated tasks directory.
/// </summary>
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,
Expand Down
14 changes: 12 additions & 2 deletions src/System.Diagnostics.Process/tests/ProcessTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,16 @@ protected void StartSleepKillWait(Process p)
/// <param name="program"></param>
/// <returns></returns>
protected static bool IsProgramInstalled(string program)
{
return GetProgramPath(program) != null;
}

/// <summary>
/// Return program path
/// </summary>
/// <param name="program"></param>
/// <returns></returns>
protected static string GetProgramPath(string program)
{
string path;
string pathEnvVar = Environment.GetEnvironmentVariable("PATH");
Expand All @@ -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;
}
}
}
31 changes: 31 additions & 0 deletions src/System.Diagnostics.Process/tests/ProcessTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1875,6 +1875,37 @@ public void TestLongProcessIsWorking()
Assert.True(p.HasExited);
}

[PlatformSpecific(TestPlatforms.AnyUnix)]
[ActiveIssue(37054, TestPlatforms.OSX)]
[Fact]
public void LongProcessNamesAreSupported()
{
string commandName = "sleep";

if (!IsProgramInstalled(commandName))
{
return;
}

string longProcessName = "123456789012345678901234567890";
string sleepCommandPathFileName = Path.Combine(TestDirectory, longProcessName);
File.Copy(GetProgramPath(commandName), sleepCommandPathFileName);

using (Process px = Process.Start(sleepCommandPathFileName, "30"))
{
var runningProcesses = Process.GetProcesses();
try
{
Assert.Contains(runningProcesses, p => p.ProcessName == longProcessName);
}
finally
{
px.Kill();
px.WaitForExit();
}
}
}

private string GetCurrentProcessName()
{
return $"{Process.GetCurrentProcess().ProcessName}.exe";
Expand Down