// Copyright Epic Games, Inc. All Rights Reserved.
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Tools.DotNETCommon
{
///
/// Includes utilities for managing processes
///
[Obsolete("Functionality in the Tools.DotNETCommon namespace is deprecated. Please reference the EpicGames.Core namespace and assembly instead.")]
public static class ProcessUtils
{
[StructLayout(LayoutKind.Sequential)]
struct PROCESSENTRY32
{
public uint dwSize;
public uint cntUsage;
public uint th32ProcessID;
public IntPtr th32DefaultHeapID;
public uint th32ModuleID;
public uint cntThreads;
public uint th32ParentProcessID;
public int pcPriClassBase;
public uint dwFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szExeFile;
};
static uint TH32CS_SNAPPROCESS = 2;
[DllImport("kernel32.dll", SetLastError = true)]
static extern SafeFileHandle CreateToolhelp32Snapshot(uint dwFlags, uint th32ProcessID);
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool Process32First(SafeFileHandle hSnapshot, ref PROCESSENTRY32 lppe);
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool Process32Next(SafeFileHandle hSnapshot, ref PROCESSENTRY32 lppe);
const uint PROCESS_TERMINATE = 0x0001;
[DllImport("kernel32.dll", SetLastError = true)]
static extern SafeProcessHandle OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint processId);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool TerminateProcess(SafeProcessHandle hProcess, uint uExitCode);
///
/// Terminates all child processes of the current process
///
public static void TerminateChildProcesses()
{
using (Process CurrentProcess = Process.GetCurrentProcess())
{
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
TerminateChildProcessesWin32(CurrentProcess.Id);
}
else
{
TerminateChildProcessesPosix(CurrentProcess.Id);
}
}
}
///
/// Win32-specific implementation of TerminateChildProcesses
///
/// The root process id of the tree to be terminated
static void TerminateChildProcessesWin32(int RootProcessId)
{
Stopwatch Timer = Stopwatch.StartNew();
HashSet UnableToOpenProcessIds = new HashSet();
for (; ; )
{
// Find a map of process id to parent process id
Dictionary ProcessIdToParentProcessId = new Dictionary();
using (SafeFileHandle SnapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0))
{
if (!SnapshotHandle.IsInvalid)
{
PROCESSENTRY32 ProcessEntry = new PROCESSENTRY32();
ProcessEntry.dwSize = (uint)Marshal.SizeOf(typeof(PROCESSENTRY32));
for (bool bResult = Process32First(SnapshotHandle, ref ProcessEntry); bResult; bResult = Process32Next(SnapshotHandle, ref ProcessEntry))
{
if (ProcessEntry.th32ParentProcessID != 0)
{
ProcessIdToParentProcessId[ProcessEntry.th32ProcessID] = ProcessEntry.th32ParentProcessID;
}
}
}
}
// Find any process ids which are a descendant from the root process it
HashSet ChildProcessIds = new HashSet();
for (bool bContinueLoop = true; bContinueLoop; )
{
bContinueLoop = false;
foreach (KeyValuePair Pair in ProcessIdToParentProcessId)
{
if (Pair.Value == RootProcessId || ChildProcessIds.Contains(Pair.Value))
{
bContinueLoop |= ChildProcessIds.Add(Pair.Key);
}
}
}
// If there's nothing running, we can quit
if (!ChildProcessIds.Any())
{
break;
}
// Check if we need to print a message about what's going on
bool bPrintStatus = false;
if (Timer == null)
{
Timer = Stopwatch.StartNew();
bPrintStatus = true;
}
else if (Timer.Elapsed > TimeSpan.FromSeconds(10.0))
{
Timer.Restart();
bPrintStatus = true;
}
// Try to kill all the child processes
bool bWaitingForProcess = false;
foreach (uint ChildProcessId in ChildProcessIds)
{
using (SafeProcessHandle ProcessHandle = OpenProcess(PROCESS_TERMINATE, false, ChildProcessId))
{
if (ProcessHandle.IsInvalid)
{
if (UnableToOpenProcessIds.Add(ChildProcessId))
{
bWaitingForProcess = true;
}
else
{
Log.TraceWarning("Unable to terminate process {0}.", ChildProcessId);
}
}
else
{
if (bPrintStatus)
{
Log.TraceInformation("Waiting for process {0} to terminate.", ChildProcessId);
}
TerminateProcess(ProcessHandle, 3);
bWaitingForProcess = true;
}
}
}
// If there wasn't anything we could do, bail out
if(!bWaitingForProcess)
{
break;
}
// Allow a short amount of time for processes to exit, then check again
Thread.Sleep(500);
}
}
///
/// Posix implementation of TerminateChildProcesses
///
/// The root process id of the tree to be terminated
static void TerminateChildProcessesPosix(int RootProcessId)
{
using (Process Process = Process.Start("pkill", String.Format("-9 -p {0}", RootProcessId)))
{
Process.WaitForExit();
}
}
}
}