Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Process.Linux: handle when /proc and the process pid namespace don't match. #100076

Merged
merged 2 commits into from
Jul 16, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ internal static partial class @procfs
{
private const string MapsFileName = "/maps";

private static string GetMapsFilePathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{MapsFileName}");
private static string GetMapsFilePathForProcess(ProcPid pid) =>
pid == ProcPid.Self ? $"{RootPath}{Self}{MapsFileName}" :
string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{MapsFileName}");

internal static ProcessModuleCollection? ParseMapsModules(int pid)
internal static ProcessModuleCollection? ParseMapsModules(ProcPid pid)
{
try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,28 @@ internal static partial class Interop
internal static partial class @procfs
{
internal const string RootPath = "/proc/";
internal const string Self = "self";
private const string StatusFileName = "/status";

// Normally the '/proc' filesystem uses the same pid namespace as the process.
// With rootless containers, it may happen that these pid namespaces do not match
// because the container doesn't have permissions to change '/proc' but it can
// create a new pid namespace.
//
// When that happens, the numeric ids used by the '/proc' filesystem no longer match with
// the process pid namespace. We can still access information for the current process
// using '/proc/self'. For other processes, we can't map pids to the proc pids so we musn't
// use '/proc' as that would return information for non-existing/wrong/inaccessible processes.
//
// The 'ProcPid' type represents a pid used by the '/proc' filesystem.
// This type provides a type-safe way to distingish the proc pids from the process pid namespace pids,
// which are passed as regular 'int's.
internal enum ProcPid : int
{
Invalid = -1,
Self = 0, // Information for the current process, accessible through '/proc/self'.
}

internal struct ParsedStatus
{
#if DEBUG
Expand All @@ -30,13 +50,15 @@ internal struct ParsedStatus
internal ulong VmPeak;
}

internal static string GetStatusFilePathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{StatusFileName}");
internal static string GetStatusFilePathForProcess(ProcPid pid) =>
pid == ProcPid.Self ? $"{RootPath}{Self}{StatusFileName}" :
string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{StatusFileName}");

internal static bool TryReadStatusFile(int pid, out ParsedStatus result)
internal static bool TryReadStatusFile(ProcPid pid, out ParsedStatus result)
{
bool b = TryParseStatusFile(GetStatusFilePathForProcess(pid), out result);
#if DEBUG
Debug.Assert(!b || result.Pid == pid, "Expected process ID from status file to match supplied pid");
Debug.Assert(!b || (ProcPid)result.Pid == pid || pid == ProcPid.Self, "Expected process ID from status file to match supplied pid");
#endif
return b;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,26 +72,38 @@ internal struct ParsedStat
//internal long cguest_time;
}

internal static string GetExeFilePathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{ExeFileName}");
internal static string GetExeFilePathForProcess(ProcPid pid) =>
pid == ProcPid.Self ? $"{RootPath}{Self}{ExeFileName}" :
string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{ExeFileName}");

internal static string GetCmdLinePathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{CmdLineFileName}");
internal static string GetCmdLinePathForProcess(ProcPid pid) =>
pid == ProcPid.Self ? $"{RootPath}{Self}{CmdLineFileName}" :
string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{CmdLineFileName}");

internal static string GetStatFilePathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{StatFileName}");
internal static string GetStatFilePathForProcess(ProcPid pid) =>
pid == ProcPid.Self ? $"{RootPath}{Self}{StatFileName}" :
string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{StatFileName}");

internal static string GetTaskDirectoryPathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{TaskDirectoryName}");
internal static string GetTaskDirectoryPathForProcess(ProcPid pid) =>
pid == ProcPid.Self ? $"{RootPath}{Self}{TaskDirectoryName}" :
string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{TaskDirectoryName}");

internal static string GetFileDescriptorDirectoryPathForProcess(int pid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{FileDescriptorDirectoryName}");
internal static string GetFileDescriptorDirectoryPathForProcess(ProcPid pid) =>
pid == ProcPid.Self ? $"{RootPath}{Self}{FileDescriptorDirectoryName}" :
string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{FileDescriptorDirectoryName}");

private static string GetStatFilePathForThread(int pid, int tid) => string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{TaskDirectoryName}{(uint)tid}{StatFileName}");
private static string GetStatFilePathForThread(ProcPid pid, int tid) =>
pid == ProcPid.Self ? string.Create(null, stackalloc char[256], $"{RootPath}{Self}{TaskDirectoryName}{(uint)tid}{StatFileName}") :
string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{TaskDirectoryName}{(uint)tid}{StatFileName}");

internal static bool TryReadStatFile(int pid, out ParsedStat result)
internal static bool TryReadStatFile(ProcPid pid, out ParsedStat result)
{
bool b = TryParseStatFile(GetStatFilePathForProcess(pid), out result);
Debug.Assert(!b || result.pid == pid, "Expected process ID from stat file to match supplied pid");
Debug.Assert(!b || pid == ProcPid.Self|| (ProcPid)result.pid == pid, "Expected process ID from stat file to match supplied pid");
return b;
}

internal static bool TryReadStatFile(int pid, int tid, out ParsedStat result)
internal static bool TryReadStatFile(ProcPid pid, int tid, out ParsedStat result)
{
bool b = TryParseStatFile(GetStatFilePathForThread(pid, tid), out result);
Debug.Assert(!b || result.pid == tid, "Expected thread ID from stat file to match supplied tid");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,14 @@ public static Process[] GetProcessesByName(string? processName, string machineNa
ArrayBuilder<Process> processes = default;
foreach (int pid in ProcessManager.EnumerateProcessIds())
{
if (Interop.procfs.TryReadStatFile(pid, out Interop.procfs.ParsedStat parsedStat))
if (ProcessManager.TryGetProcPid(pid, out Interop.procfs.ProcPid procPid) &&
Interop.procfs.TryReadStatFile(procPid, out Interop.procfs.ParsedStat parsedStat))
{
string actualProcessName = GetUntruncatedProcessName(ref parsedStat);
string actualProcessName = GetUntruncatedProcessName(procPid, ref parsedStat);
if ((processName == "" || string.Equals(processName, actualProcessName, StringComparison.OrdinalIgnoreCase)) &&
Interop.procfs.TryReadStatusFile(pid, out Interop.procfs.ParsedStatus parsedStatus))
Interop.procfs.TryReadStatusFile(procPid, out Interop.procfs.ParsedStatus parsedStatus))
{
ProcessInfo processInfo = ProcessManager.CreateProcessInfo(ref parsedStat, ref parsedStatus, actualProcessName);
ProcessInfo processInfo = ProcessManager.CreateProcessInfo(procPid, ref parsedStat, ref parsedStatus, actualProcessName);
processes.Add(new Process(machineName, isRemoteMachine: false, pid, processInfo));
}
}
Expand Down Expand Up @@ -160,7 +161,11 @@ partial void EnsureHandleCountPopulated()
{
return;
}
string path = Interop.procfs.GetFileDescriptorDirectoryPathForProcess(_processId);
if (!ProcessManager.TryGetProcPid(_processId, out Interop.procfs.ProcPid procPid))
{
return;
}
string path = Interop.procfs.GetFileDescriptorDirectoryPathForProcess(procPid);
if (Directory.Exists(path))
{
try
Expand Down Expand Up @@ -250,18 +255,19 @@ private static void SetWorkingSetLimitsCore(IntPtr? newMin, IntPtr? newMax, out
#pragma warning restore IDE0060

/// <summary>Gets the path to the executable for 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? GetExePath(int processId = -1)
/// <param name="procPid">The pid for the target process.</param>
internal static string? GetExePath(Interop.procfs.ProcPid procPid)
{
return processId == -1 ? Environment.ProcessPath :
Interop.Sys.ReadLink(Interop.procfs.GetExeFilePathForProcess(processId));
return procPid == Interop.procfs.ProcPid.Self ? Environment.ProcessPath :
Interop.Sys.ReadLink(Interop.procfs.GetExeFilePathForProcess(procPid));
}

/// <summary>Gets the name that was used to start the process, or null if it could not be retrieved.</summary>
/// <param name="procPid">The pid for the target process.</param>
/// <param name="stat">The stat for the target process.</param>
internal static string GetUntruncatedProcessName(ref Interop.procfs.ParsedStat stat)
internal static string GetUntruncatedProcessName(Interop.procfs.ProcPid procPid, ref Interop.procfs.ParsedStat stat)
{
string cmdLineFilePath = Interop.procfs.GetCmdLinePathForProcess(stat.pid);
string cmdLineFilePath = Interop.procfs.GetCmdLinePathForProcess(procPid);

byte[]? rentedArray = null;
try
Expand Down Expand Up @@ -362,7 +368,7 @@ private Interop.procfs.ParsedStat GetStat()
{
EnsureState(State.HaveNonExitedId);
Interop.procfs.ParsedStat stat;
if (!Interop.procfs.TryReadStatFile(_processId, out stat))
if (!ProcessManager.TryReadStatFile(_processId, out stat))
{
throw new Win32Exception(SR.ProcessInformationUnavailable);
}
Expand Down
Loading
Loading