File: System\Diagnostics\ProcessManager.Linux.cs
Web Access
Project: src\src\libraries\System.Diagnostics.Process\src\System.Diagnostics.Process.csproj (System.Diagnostics.Process)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Globalization;
using System.IO;
 
namespace System.Diagnostics
{
    internal static partial class ProcessManager
    {
        private static volatile int _procMatchesPidNamespace;
 
        /// <summary>Gets the IDs of all processes on the current machine.</summary>
        public static int[] GetProcessIds() => new List<int>(EnumerateProcessIds()).ToArray();
 
        /// <summary>Gets process infos for each process on the specified machine.</summary>
        /// <param name="processNameFilter">Optional process name to use as an inclusion filter.</param>
        /// <param name="machineName">The target machine.</param>
        /// <returns>An array of process infos, one per found process.</returns>
        public static ProcessInfo[] GetProcessInfos(string? processNameFilter, string machineName)
        {
            Debug.Assert(processNameFilter is null, "Not used on Linux");
            ThrowIfRemoteMachine(machineName);
 
            // Iterate through all process IDs to load information about each process
            IEnumerable<int> pids = EnumerateProcessIds();
            ArrayBuilder<ProcessInfo> processes = default;
            foreach (int pid in pids)
            {
                ProcessInfo? pi = CreateProcessInfo(pid);
                if (pi != null)
                {
                    processes.Add(pi);
                }
            }
 
            return processes.ToArray();
        }
 
        /// <summary>Gets an array of module infos for the specified process.</summary>
        /// <param name="processId">The ID of the process whose modules should be enumerated.</param>
        /// <returns>The array of modules.</returns>
        internal static ProcessModuleCollection GetModules(int processId)
        {
            ProcessModuleCollection? modules = null;
            if (TryGetProcPid(processId, out Interop.procfs.ProcPid procPid))
            {
                modules = Interop.procfs.ParseMapsModules(procPid);
 
                // Move the main executable module to be the first in the list if it's not already
                if (modules is not null && Process.GetExePath(procPid) is string exePath)
                {
                    for (int i = 0; i < modules.Count; i++)
                    {
                        ProcessModule module = modules[i];
                        if (module.FileName == exePath)
                        {
                            if (i > 0)
                            {
                                modules.RemoveAt(i);
                                modules.Insert(0, module);
                            }
                            break;
                        }
                    }
                }
            }
 
            return modules ?? new(capacity: 0);
        }
 
        /// <summary>
        /// Creates a ProcessInfo from the specified process ID.
        /// </summary>
        internal static ProcessInfo? CreateProcessInfo(int pid)
        {
            if (TryGetProcPid(pid, out Interop.procfs.ProcPid procPid) &&
                Interop.procfs.TryReadStatFile(procPid, out Interop.procfs.ParsedStat stat))
            {
                Interop.procfs.TryReadStatusFile(procPid, out Interop.procfs.ParsedStatus status);
                return CreateProcessInfo(procPid, ref stat, ref status);
            }
            return 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.ProcPid procPid, ref Interop.procfs.ParsedStat procFsStat, ref Interop.procfs.ParsedStatus procFsStatus, string? processName = null)
        {
            int pid = procFsStat.pid;
 
            var pi = new ProcessInfo()
            {
                ProcessId = pid,
                ProcessName = processName ?? Process.GetUntruncatedProcessName(procPid, ref procFsStat) ?? string.Empty,
                BasePriority = (int)procFsStat.nice,
                SessionId = procFsStat.session,
                PoolPagedBytes = (long)procFsStatus.VmSwap,
                VirtualBytes = (long)procFsStatus.VmSize,
                VirtualBytesPeak = (long)procFsStatus.VmPeak,
                WorkingSetPeak = (long)procFsStatus.VmHWM,
                WorkingSet = (long)procFsStatus.VmRSS,
                PageFileBytes = (long)procFsStatus.VmSwap,
                PrivateBytes = (long)procFsStatus.VmData,
                // We don't currently fill in the other values.
                // A few of these could probably be filled in from getrusage,
                // but only for the current process or its children, not for
                // arbitrary other processes.
            };
 
            // Then read through /proc/pid/task/ to find each thread in the process...
            string tasksDir = Interop.procfs.GetTaskDirectoryPathForProcess(procPid);
            try
            {
                foreach (string taskDir in Directory.EnumerateDirectories(tasksDir))
                {
                    // ...and read its associated /proc/pid/task/tid/stat file to create a ThreadInfo
                    string dirName = Path.GetFileName(taskDir);
                    int tid;
                    Interop.procfs.ParsedStat stat;
                    if (int.TryParse(dirName, NumberStyles.Integer, CultureInfo.InvariantCulture, out tid) &&
                        Interop.procfs.TryReadStatFile(procPid, tid, out stat))
                    {
                        pi._threadInfoList.Add(new ThreadInfo()
                        {
                            _processId = pid,
                            _threadId = (ulong)tid,
                            _basePriority = pi.BasePriority,
                            _currentPriority = (int)stat.nice,
                            _startAddress = null,
                            _threadState = ProcFsStateToThreadState(stat.state),
                            _threadWaitReason = ThreadWaitReason.Unknown
                        });
                    }
                }
            }
            catch (IOException)
            {
                // Between the time that we get an ID and the time that we try to read the associated
                // directories and files in procfs, the process could be gone.
            }
 
            // Finally return what we've built up
            return pi;
        }
 
        // ----------------------------------
        // ---- Unix PAL layer ends here ----
        // ----------------------------------
 
        /// <summary>Enumerates the IDs of all processes on the current machine.</summary>
        internal static IEnumerable<int> EnumerateProcessIds()
        {
            if (ProcMatchesPidNamespace)
            {
                // Parse /proc for any directory that's named with a number.  Each such
                // directory represents a process.
                foreach (string procDir in Directory.EnumerateDirectories(Interop.procfs.RootPath))
                {
                    string dirName = Path.GetFileName(procDir);
                    int pid;
                    if (int.TryParse(dirName, NumberStyles.Integer, CultureInfo.InvariantCulture, out pid))
                    {
                        Debug.Assert(pid >= 0);
                        yield return pid;
                    }
                }
            }
            else
            {
                // Limit to our own process. For other processes, the pids from /proc don't match with those in the process namespace.
                yield return Environment.ProcessId;
            }
        }
 
        /// <summary>Gets a ThreadState to represent the value returned from the status field of /proc/pid/stat.</summary>
        /// <param name="c">The status field value.</param>
        /// <returns></returns>
        private static ThreadState ProcFsStateToThreadState(char c)
        {
            // Information on these in fs/proc/array.c
            // `man proc` does not document them all
            switch (c)
            {
                case 'R': // Running
                    return ThreadState.Running;
 
                case 'D': // Waiting on disk
                case 'P': // Parked
                case 'S': // Sleeping in a wait
                case 't': // Tracing/debugging
                case 'T': // Stopped on a signal
                    return ThreadState.Wait;
 
                case 'x': // dead
                case 'X': // Dead
                case 'Z': // Zombie
                    return ThreadState.Terminated;
 
                case 'W': // Paging or waking
                case 'K': // Wakekill
                    return ThreadState.Transition;
 
                case 'I': // Idle
                    return ThreadState.Ready;
 
                default:
                    Debug.Fail($"Unexpected status character: {c}");
                    return ThreadState.Unknown;
            }
        }
 
        internal static bool TryReadStatFile(int pid, out Interop.procfs.ParsedStat stat)
        {
            if (!TryGetProcPid(pid, out Interop.procfs.ProcPid procPid))
            {
                stat = default;
                return false;
            }
            return Interop.procfs.TryReadStatFile(procPid, out stat);
        }
 
        internal static bool TryReadStatusFile(int pid, out Interop.procfs.ParsedStatus status)
        {
            if (!TryGetProcPid(pid, out Interop.procfs.ProcPid procPid))
            {
                status = default;
                return false;
            }
            return Interop.procfs.TryReadStatusFile(procPid, out status);
        }
 
        internal static bool TryReadStatFile(int pid, int tid, out Interop.procfs.ParsedStat stat)
        {
            if (!TryGetProcPid(pid, out Interop.procfs.ProcPid procPid))
            {
                stat = default;
                return false;
            }
            return Interop.procfs.TryReadStatFile(procPid, tid, out stat);
        }
 
        internal static bool TryGetProcPid(int pid, out Interop.procfs.ProcPid procPid)
        {
            // Use '/proc/self' for the current process.
            if (pid == Environment.ProcessId)
            {
                procPid = Interop.procfs.ProcPid.Self;
                return true;
            }
 
            if (ProcMatchesPidNamespace)
            {
                procPid = (Interop.procfs.ProcPid)pid;
                return true;
            }
 
            // We can't map a process namespace pid to a procfs pid.
            procPid = Interop.procfs.ProcPid.Invalid;
            return false;
        }
 
        internal static bool ProcMatchesPidNamespace
        {
            get
            {
                // _procMatchesPidNamespace is set to:
                // - 0: when uninitialized,
                // - 1: '/proc' and the process pid namespace match,
                // - 2: when they don't match.
                if (_procMatchesPidNamespace == 0)
                {
                    // '/proc/self' is a symlink to the pid used by '/proc' for the current process.
                    // We compare it with the pid of the current process to see if the '/proc' and pid namespace match up.
                    int? procSelfPid = null;
                    if (Interop.Sys.ReadLink($"{Interop.procfs.RootPath}{Interop.procfs.Self}") is string target &&
                        int.TryParse(target, out int pid))
                    {
                        procSelfPid = pid;
                    }
                    Debug.Assert(procSelfPid.HasValue);
 
                    _procMatchesPidNamespace = !procSelfPid.HasValue || procSelfPid == Environment.ProcessId ? 1 : 2;
                }
                return _procMatchesPidNamespace == 1;
            }
        }
    }
}