{
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
// 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;
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;
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
/// 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();
}
// 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;
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);
// 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;
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));
}
}
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);
+ byte[] toReturn = rentedArray;
+ buffer = rentedArray = tmp;
+ if (rentedArray != null)
+ {
+ ArrayPool<byte>.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<byte>.Shared.Return(rentedArray);
+ }
+ }
+ }
+
// ----------------------------------
// ---- Unix PAL layer ends here ----
// ----------------------------------
/// <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)
{
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,
/// <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");
char separator = PlatformDetection.IsWindows ? ';' : ':';
path = Path.Combine(subPath, program);
if (File.Exists(path))
{
- return true;
+ return path;
}
}
}
- return false;
+ return null;
}
}
}
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";