Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 1b6f45c

Browse files
tmdswtgodbe
authored andcommitted
Linux: ProcessName: return script name instead of interpreter program (#37294)
* Linux: ProcessName: return script name instead of interpreter program Fixes https://github.com/dotnet/corefx/issues/37198 Regression by #37144 * Add ProcessNameMatchesScriptName test * ProcessNameMatchesScriptName: don't run on OSX * LongProcessNamesAreSupported: don't run on Alpine * Remove ActiveIssue attributes
1 parent b885c22 commit 1b6f45c

File tree

7 files changed

+83
-28
lines changed

7 files changed

+83
-28
lines changed

src/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ internal struct ParsedStat
3333
// the MoveNext() with the appropriate ParseNext* call and assignment.
3434

3535
internal int pid;
36-
// internal string comm;
36+
internal string comm;
3737
internal char state;
3838
internal int ppid;
3939
//internal int pgrp;
@@ -238,7 +238,7 @@ internal static bool TryParseStatFile(string statFilePath, out ParsedStat result
238238
var results = default(ParsedStat);
239239

240240
results.pid = parser.ParseNextInt32();
241-
parser.MoveAndExtractNextInOuterParens(extractValue: false); // comm
241+
results.comm = parser.MoveAndExtractNextInOuterParens();
242242
results.state = parser.ParseNextChar();
243243
results.ppid = parser.ParseNextInt32();
244244
parser.MoveNextOrFail(); // pgrp

src/Common/src/System/IO/StringParser.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public string MoveAndExtractNext()
9898
/// in the string. The extracted value will be everything between (not including) those parentheses.
9999
/// </summary>
100100
/// <returns></returns>
101-
public string MoveAndExtractNextInOuterParens(bool extractValue = true)
101+
public string MoveAndExtractNextInOuterParens()
102102
{
103103
// Move to the next position
104104
MoveNextOrFail();
@@ -118,7 +118,7 @@ public string MoveAndExtractNextInOuterParens(bool extractValue = true)
118118
}
119119

120120
// Extract the contents of the parens, then move our ending position to be after the paren
121-
string result = extractValue ? _buffer.Substring(_startIndex + 1, lastParen - _startIndex - 1) : null;
121+
string result = _buffer.Substring(_startIndex + 1, lastParen - _startIndex - 1);
122122
_endIndex = lastParen + 1;
123123

124124
return result;

src/Common/tests/Tests/Interop/procfsTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public static void ParseValidStatFiles_Success(
4949
Assert.True(Interop.procfs.TryParseStatFile(path, out result, new ReusableTextReader()));
5050

5151
Assert.Equal(expectedPid, result.pid);
52+
Assert.Equal(expectedComm, result.comm);
5253
Assert.Equal(expectedState, result.state);
5354
Assert.Equal(expectedSession, result.session);
5455
Assert.Equal(expectedUtime, result.utime);

src/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@ public static Process[] GetProcessesByName(string processName, string machineNam
3030
var processes = new List<Process>();
3131
foreach (int pid in ProcessManager.EnumerateProcessIds())
3232
{
33-
if (string.Equals(processName, Process.GetProcessName(pid), StringComparison.OrdinalIgnoreCase))
33+
Interop.procfs.ParsedStat parsedStat;
34+
if (Interop.procfs.TryReadStatFile(pid, out parsedStat, reusableReader) &&
35+
string.Equals(processName, Process.GetUntruncatedProcessName(ref parsedStat), StringComparison.OrdinalIgnoreCase))
3436
{
35-
ProcessInfo processInfo = ProcessManager.CreateProcessInfo(pid, reusableReader, processName);
37+
ProcessInfo processInfo = ProcessManager.CreateProcessInfo(ref parsedStat, reusableReader, processName);
3638
processes.Add(new Process(machineName, false, processInfo.ProcessId, processInfo));
3739
}
3840
}
@@ -256,12 +258,10 @@ internal static string GetExePath(int processId = -1)
256258
}
257259

258260
/// <summary>Gets the name that was used to start the process, or null if it could not be retrieved.</summary>
259-
/// <param name="processId">The pid for the target process, or -1 for the current process.</param>
260-
internal static string GetProcessName(int processId = -1)
261+
/// <param name="stat">The stat for the target process.</param>
262+
internal static string GetUntruncatedProcessName(ref Interop.procfs.ParsedStat stat)
261263
{
262-
string cmdLineFilePath = processId == -1 ?
263-
Interop.procfs.SelfCmdLineFilePath :
264-
Interop.procfs.GetCmdLinePathForProcess(processId);
264+
string cmdLineFilePath = Interop.procfs.GetCmdLinePathForProcess(stat.pid);
265265

266266
byte[] rentedArray = null;
267267
try
@@ -282,7 +282,7 @@ internal static string GetProcessName(int processId = -1)
282282
buffer.CopyTo(tmp);
283283
byte[] toReturn = rentedArray;
284284
buffer = rentedArray = tmp;
285-
if (rentedArray != null)
285+
if (toReturn != null)
286286
{
287287
ArrayPool<byte>.Shared.Return(toReturn);
288288
}
@@ -293,26 +293,40 @@ internal static string GetProcessName(int processId = -1)
293293
bytesRead += n;
294294

295295
// cmdline contains the argv array separated by '\0' bytes.
296-
// we determine the process name using argv[0].
297-
int argv0End = buffer.Slice(0, bytesRead).IndexOf((byte)'\0');
298-
if (argv0End != -1)
296+
// stat.comm contains a possibly truncated version of the process name.
297+
// When the program is a native executable, the process name will be in argv[0].
298+
// When the program is a script, argv[0] contains the interpreter, and argv[1] contains the script name.
299+
Span<byte> argRemainder = buffer.Slice(0, bytesRead);
300+
int argEnd = argRemainder.IndexOf((byte)'\0');
301+
if (argEnd != -1)
299302
{
300-
// Strip directory names from argv[0].
301-
int nameStart = buffer.Slice(0, argv0End).LastIndexOf((byte)'/') + 1;
303+
// Check if argv[0] has the process name.
304+
string name = GetUntruncatedNameFromArg(argRemainder.Slice(0, argEnd), prefix: stat.comm);
305+
if (name != null)
306+
{
307+
return name;
308+
}
302309

303-
return Encoding.UTF8.GetString(buffer.Slice(nameStart, argv0End - nameStart));
310+
// Check if argv[1] has the process name.
311+
argRemainder = argRemainder.Slice(argEnd + 1);
312+
argEnd = argRemainder.IndexOf((byte)'\0');
313+
if (argEnd != -1)
314+
{
315+
name = GetUntruncatedNameFromArg(argRemainder.Slice(0, argEnd), prefix: stat.comm);
316+
return name ?? stat.comm;
317+
}
304318
}
305319

306320
if (n == 0)
307321
{
308-
return null;
322+
return stat.comm;
309323
}
310324
}
311325
}
312326
}
313327
catch (IOException)
314328
{
315-
return null;
329+
return stat.comm;
316330
}
317331
finally
318332
{
@@ -321,6 +335,22 @@ internal static string GetProcessName(int processId = -1)
321335
ArrayPool<byte>.Shared.Return(rentedArray);
322336
}
323337
}
338+
339+
string GetUntruncatedNameFromArg(Span<byte> arg, string prefix)
340+
{
341+
// Strip directory names from arg.
342+
int nameStart = arg.LastIndexOf((byte)'/') + 1;
343+
string argString = Encoding.UTF8.GetString(arg.Slice(nameStart));
344+
345+
if (argString.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
346+
{
347+
return argString;
348+
}
349+
else
350+
{
351+
return null;
352+
}
353+
}
324354
}
325355

326356
// ----------------------------------

src/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Linux.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ internal static ProcessModuleCollection GetModules(int processId)
108108
/// <summary>
109109
/// Creates a ProcessInfo from the specified process ID.
110110
/// </summary>
111-
internal static ProcessInfo CreateProcessInfo(int pid, ReusableTextReader reusableReader = null, string processName = null)
111+
internal static ProcessInfo CreateProcessInfo(int pid, ReusableTextReader reusableReader = null)
112112
{
113113
if (reusableReader == null)
114114
{
@@ -117,21 +117,21 @@ internal static ProcessInfo CreateProcessInfo(int pid, ReusableTextReader reusab
117117

118118
Interop.procfs.ParsedStat stat;
119119
return Interop.procfs.TryReadStatFile(pid, out stat, reusableReader) ?
120-
CreateProcessInfo(stat, reusableReader, processName) :
120+
CreateProcessInfo(ref stat, reusableReader) :
121121
null;
122122
}
123123

124124
/// <summary>
125125
/// Creates a ProcessInfo from the data parsed from a /proc/pid/stat file and the associated tasks directory.
126126
/// </summary>
127-
internal static ProcessInfo CreateProcessInfo(Interop.procfs.ParsedStat procFsStat, ReusableTextReader reusableReader, string processName)
127+
internal static ProcessInfo CreateProcessInfo(ref Interop.procfs.ParsedStat procFsStat, ReusableTextReader reusableReader, string processName = null)
128128
{
129129
int pid = procFsStat.pid;
130130

131131
var pi = new ProcessInfo()
132132
{
133133
ProcessId = pid,
134-
ProcessName = processName ?? Process.GetProcessName(pid) ?? string.Empty,
134+
ProcessName = processName ?? Process.GetUntruncatedProcessName(ref procFsStat) ?? string.Empty,
135135
BasePriority = (int)procFsStat.nice,
136136
VirtualBytes = (long)procFsStat.vsize,
137137
WorkingSet = procFsStat.rss * Environment.SystemPageSize,

src/System.Diagnostics.Process/tests/ProcessTests.Unix.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ public void ProcessStart_UseShellExecute_OnUnix_OpenMissingFile_DoesNotThrow()
124124
}
125125
}
126126

127-
[ActiveIssue(37198)]
128127
[Theory, InlineData(true), InlineData(false)]
129128
[OuterLoop("Opens program")]
130129
public void ProcessStart_UseShellExecute_OnUnix_SuccessWhenProgramInstalled(bool isFolder)
@@ -157,6 +156,31 @@ public void ProcessStart_UseShellExecute_OnUnix_SuccessWhenProgramInstalled(bool
157156
}
158157
}
159158

159+
[Fact]
160+
[PlatformSpecific(~TestPlatforms.OSX)] // On OSX, ProcessName returns the script interpreter.
161+
public void ProcessNameMatchesScriptName()
162+
{
163+
string scriptName = GetTestFileName();
164+
string filename = Path.Combine(TestDirectory, scriptName);
165+
File.WriteAllText(filename, $"#!/bin/sh\nsleep 600\n"); // sleep 10 min.
166+
// set x-bit
167+
int mode = Convert.ToInt32("744", 8);
168+
Assert.Equal(0, chmod(filename, mode));
169+
170+
using (var process = Process.Start(new ProcessStartInfo { FileName = filename }))
171+
{
172+
try
173+
{
174+
Assert.Equal(scriptName, process.ProcessName);
175+
}
176+
finally
177+
{
178+
process.Kill();
179+
process.WaitForExit();
180+
}
181+
}
182+
}
183+
160184
[Fact]
161185
[PlatformSpecific(TestPlatforms.Linux)] // s_allowedProgramsToRun is Linux specific
162186
public void ProcessStart_UseShellExecute_OnUnix_FallsBackWhenNotRealExecutable()
@@ -294,7 +318,6 @@ public void ProcessStart_OpenFileOnLinux_UsesSpecifiedProgram(string programToOp
294318
}
295319
}
296320

297-
[ActiveIssue(37198)]
298321
[Theory, InlineData("vi")]
299322
[PlatformSpecific(TestPlatforms.Linux)]
300323
[OuterLoop("Opens program")]

src/System.Diagnostics.Process/tests/ProcessTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1921,15 +1921,16 @@ public void TestLongProcessIsWorking()
19211921
Assert.True(p.HasExited);
19221922
}
19231923

1924-
[ActiveIssue(37198)]
19251924
[PlatformSpecific(TestPlatforms.AnyUnix)]
19261925
[ActiveIssue(37054, TestPlatforms.OSX)]
19271926
[Fact]
19281927
public void LongProcessNamesAreSupported()
19291928
{
1929+
// Alpine implements sleep as a symlink to the busybox executable.
1930+
// If we rename it, the program will no longer sleep.
19301931
if (PlatformDetection.IsAlpine)
19311932
{
1932-
return; // https://github.com/dotnet/corefx/issues/37054
1933+
return;
19331934
}
19341935

19351936
string programPath = GetProgramPath("sleep");

0 commit comments

Comments
 (0)