📦 deps(thirdparty): update snapshots
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Executable,
|
||||
[Parameter(Mandatory = $true)][string]$ArgumentsBase64,
|
||||
[Parameter(Mandatory = $true)][string]$TraceOutput,
|
||||
[Parameter(Mandatory = $true)][string]$ResultOutput,
|
||||
[Parameter(Mandatory = $true)][ValidatePattern('^AASVerifier-[A-Za-z0-9-]+$')][string]$SessionName,
|
||||
[Parameter(Mandatory = $true)][ValidateRange(1, 900000)][int]$CandidateTimeoutMilliseconds
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$jobSource = Join-Path $PSScriptRoot "windows-job.cs"
|
||||
if (!(Test-Path -LiteralPath $jobSource -PathType Leaf)) { throw "Windows Job Object helper is unavailable" }
|
||||
Add-Type -Path $jobSource
|
||||
$etl = Join-Path ([IO.Path]::GetDirectoryName($TraceOutput)) "$SessionName.etl"
|
||||
$csv = Join-Path ([IO.Path]::GetDirectoryName($TraceOutput)) "$SessionName.csv"
|
||||
$arguments = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($ArgumentsBase64)) | ConvertFrom-Json
|
||||
$providers = @(
|
||||
"Microsoft-Windows-Kernel-Process",
|
||||
"Microsoft-Windows-Kernel-File",
|
||||
"Microsoft-Windows-Winsock-AFD",
|
||||
"Microsoft-Windows-DNS-Client"
|
||||
)
|
||||
$jobProcess = $null
|
||||
|
||||
try {
|
||||
& logman.exe create trace $SessionName -o $etl -ets | Out-Null
|
||||
foreach ($provider in $providers) {
|
||||
& logman.exe update trace $SessionName -p $provider 0xffffffffffffffff 0xff -ets | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { throw "Unable to enable ETW provider $provider" }
|
||||
}
|
||||
$started = [DateTimeOffset]::UtcNow
|
||||
$jobProcess = [AasVerifier.JobProcess]::Start(
|
||||
$Executable,
|
||||
[string[]]$arguments,
|
||||
"$ResultOutput.stdout",
|
||||
"$ResultOutput.stderr"
|
||||
)
|
||||
$waitResult = [AasVerifier.JobProcess]::Wait($jobProcess, $CandidateTimeoutMilliseconds)
|
||||
$timedOut = $waitResult -eq [AasVerifier.JobProcess]::WaitTimeout
|
||||
if ($timedOut) {
|
||||
[AasVerifier.JobProcess]::Terminate($jobProcess, 124)
|
||||
$waitResult = [AasVerifier.JobProcess]::Wait($jobProcess, 5000)
|
||||
}
|
||||
if ($waitResult -ne [AasVerifier.JobProcess]::WaitObject0) {
|
||||
throw "Windows Job Object did not reach an empty process-tree state"
|
||||
}
|
||||
$rootExitCode = [AasVerifier.JobProcess]::ExitCode($jobProcess)
|
||||
$jobTotalProcesses = [AasVerifier.JobProcess]::TotalProcesses($jobProcess)
|
||||
$ended = [DateTimeOffset]::UtcNow
|
||||
& logman.exe stop $SessionName -ets | Out-Null
|
||||
& tracerpt.exe $etl -of CSV -o $csv -y | Out-Null
|
||||
if ($LASTEXITCODE -ne 0 -or !(Test-Path -LiteralPath $csv -PathType Leaf)) { throw "ETW trace export failed" }
|
||||
$rootPid = $jobProcess.ProcessId
|
||||
$lines = New-Object System.Collections.Generic.List[string]
|
||||
for ($processIndex = 1; $processIndex -lt $jobTotalProcesses; $processIndex++) {
|
||||
$lines.Add("process|job-object|index=$processIndex")
|
||||
}
|
||||
$childPids = New-Object System.Collections.Generic.HashSet[int]
|
||||
$childPids.Add($rootPid) | Out-Null
|
||||
function Get-IntegerField($row, [string[]]$patterns) {
|
||||
foreach ($property in $row.PSObject.Properties) {
|
||||
foreach ($pattern in $patterns) {
|
||||
if ($property.Name -match $pattern) {
|
||||
$parsed = 0
|
||||
$raw = ([string]$property.Value).Trim()
|
||||
if ([int]::TryParse($raw, [ref]$parsed)) { return $parsed }
|
||||
if ($raw -match '^0[xX][0-9a-fA-F]+$') {
|
||||
try { return [Convert]::ToInt32($raw.Substring(2), 16) } catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
function Convert-ObservedInteger([string]$raw) {
|
||||
$value = $raw.Trim().Trim('"').Trim("'")
|
||||
$parsed = 0
|
||||
if ([int]::TryParse($value, [ref]$parsed)) { return $parsed }
|
||||
if ($value -match '^0[xX][0-9a-fA-F]+$') {
|
||||
try { return [Convert]::ToInt32($value.Substring(2), 16) } catch { }
|
||||
}
|
||||
return 0
|
||||
}
|
||||
function Get-PayloadInteger($row, [string]$name) {
|
||||
foreach ($property in $row.PSObject.Properties) {
|
||||
if (([string]$property.Name).Trim() -ieq $name) {
|
||||
$direct = Convert-ObservedInteger ([string]$property.Value)
|
||||
if ($direct -gt 0) { return $direct }
|
||||
}
|
||||
}
|
||||
$payloadPattern = '(?i)(?:^|[;,{\s])["'']?' + [regex]::Escape($name) + '["'']?\s*[:=]\s*["'']?(0[xX][0-9a-fA-F]+|[0-9]+)["'']?'
|
||||
foreach ($property in $row.PSObject.Properties) {
|
||||
$text = [string]$property.Value
|
||||
$match = [regex]::Match($text, $payloadPattern)
|
||||
if ($match.Success) { return Convert-ObservedInteger $match.Groups[1].Value }
|
||||
}
|
||||
return 0
|
||||
}
|
||||
$totalRows = 0
|
||||
$rootRows = 0
|
||||
$networkRows = 0
|
||||
$writeRows = 0
|
||||
$winsockCreateRows = 0
|
||||
$winsockDecodedPids = New-Object System.Collections.Generic.List[int]
|
||||
$processStartRows = 0
|
||||
$rootStopRows = 0
|
||||
$rootExitObserved = $false
|
||||
$postRootDescendantWriteRows = 0
|
||||
$rootEventSamples = New-Object System.Collections.Generic.List[string]
|
||||
foreach ($row in (Import-Csv -LiteralPath $csv)) {
|
||||
$totalRows++
|
||||
$serialized = $row | ConvertTo-Json -Compress
|
||||
$eventName = (($row.PSObject.Properties | ForEach-Object { "$($_.Name)=$($_.Value)" }) -join " ")
|
||||
$providerName = ([string]$row.'Event Name').Trim()
|
||||
$eventType = ([string]$row.Type).Trim()
|
||||
$eventId = Get-IntegerField $row @("(?i)^Event ID$")
|
||||
$opcode = Get-IntegerField $row @("(?i)^Opcode$")
|
||||
if ($providerName -eq 'Microsoft-Windows-Kernel-Process' -and $eventType -eq 'Start' -and $opcode -eq 1) {
|
||||
$processStartRows++
|
||||
# Process Start events are emitted in the creating process context, so the
|
||||
# ETW header PID is the parent. The payload ProcessID is the new process.
|
||||
$parentPid = Get-IntegerField $row @("(?i)^PID$")
|
||||
if ($parentPid -eq 0) { $parentPid = Get-PayloadInteger $row 'ParentProcessID' }
|
||||
$newPid = Get-PayloadInteger $row 'ProcessID'
|
||||
if ($childPids.Contains($parentPid) -and $newPid -gt 0 -and !$childPids.Contains($newPid)) {
|
||||
$childPids.Add($newPid) | Out-Null
|
||||
$lines.Add("process|$newPid|parent=$parentPid")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if ($providerName -eq 'Microsoft-Windows-Kernel-Process' -and $eventType -eq 'Stop' -and $opcode -eq 2) {
|
||||
$stoppedPid = Get-PayloadInteger $row 'ProcessID'
|
||||
if ($stoppedPid -eq 0) { $stoppedPid = Get-IntegerField $row @("(?i)^PID$") }
|
||||
if ($stoppedPid -eq $rootPid) {
|
||||
$rootStopRows++
|
||||
$rootExitObserved = $true
|
||||
}
|
||||
continue
|
||||
}
|
||||
if ($providerName -like 'Microsoft-Windows-Winsock*' -and $eventId -eq 1000) {
|
||||
$winsockCreateRows++
|
||||
$userModePid = Get-PayloadInteger $row 'UserModePid'
|
||||
if ($userModePid -gt 0 -and $winsockDecodedPids.Count -lt 8 -and !$winsockDecodedPids.Contains($userModePid)) {
|
||||
$winsockDecodedPids.Add($userModePid)
|
||||
}
|
||||
$networkPid = Get-IntegerField $row @("(?i)^Process.*Id$", "(?i)^PID$")
|
||||
if ($childPids.Contains($networkPid) -or $childPids.Contains($userModePid)) {
|
||||
$networkRows++
|
||||
$lines.Add("network|$networkPid|provider=$providerName;event=$eventId")
|
||||
}
|
||||
continue
|
||||
}
|
||||
$pidValue = Get-IntegerField $row @("(?i)^Process.*Id$", "(?i)^PID$")
|
||||
if (!$childPids.Contains($pidValue)) { continue }
|
||||
$rootRows++
|
||||
if ($rootEventSamples.Count -lt 12) {
|
||||
$safeIdentity = (($row.PSObject.Properties | Where-Object {
|
||||
$_.Name -match '(?i)^(Event Name|Type|Event ID|Opcode|Task|Keyword|PID|Provider Name|Provider Guid)$'
|
||||
} | ForEach-Object { "$($_.Name)=$($_.Value)" }) -join ';')
|
||||
if ($safeIdentity -and !$rootEventSamples.Contains($safeIdentity)) { $rootEventSamples.Add($safeIdentity) }
|
||||
}
|
||||
if ($providerName -eq 'Microsoft-Windows-DNS-Client') {
|
||||
$networkRows++
|
||||
$lines.Add("network|$pidValue|provider=$providerName;event=$eventId")
|
||||
}
|
||||
elseif ($providerName -eq 'Microsoft-Windows-Kernel-File' -and $eventId -in @(16,17,18,19)) {
|
||||
$writeRows++
|
||||
if ($rootExitObserved -and $pidValue -ne $rootPid) { $postRootDescendantWriteRows++ }
|
||||
$lines.Add("write|$pidValue|provider=$providerName;event=$eventId")
|
||||
}
|
||||
}
|
||||
[IO.File]::WriteAllLines($TraceOutput, $lines, (New-Object Text.UTF8Encoding($false)))
|
||||
$receipt = [ordered]@{
|
||||
code = $(if ($timedOut) { 124 } else { $rootExitCode })
|
||||
signal = $null
|
||||
stdout = [IO.File]::ReadAllText("$ResultOutput.stdout")
|
||||
stderr = [IO.File]::ReadAllText("$ResultOutput.stderr")
|
||||
timedOut = $timedOut
|
||||
outputLimitExceeded = $false
|
||||
startedAt = $started.ToString("o")
|
||||
endedAt = $ended.ToString("o")
|
||||
observerDiagnostics = [ordered]@{
|
||||
totalRows = $totalRows
|
||||
rootRows = $rootRows
|
||||
networkRows = $networkRows
|
||||
writeRows = $writeRows
|
||||
winsockCreateRows = $winsockCreateRows
|
||||
winsockDecodedPids = @($winsockDecodedPids)
|
||||
processStartRows = $processStartRows
|
||||
rootStopRows = $rootStopRows
|
||||
postRootDescendantWriteRows = $postRootDescendantWriteRows
|
||||
rootEventSamples = @($rootEventSamples)
|
||||
sessionName = $SessionName
|
||||
processTreeTimedOut = $timedOut
|
||||
processTreeEmpty = $waitResult -eq [AasVerifier.JobProcess]::WaitObject0
|
||||
jobTotalProcesses = $jobTotalProcesses
|
||||
}
|
||||
}
|
||||
[IO.File]::WriteAllText($ResultOutput, ($receipt | ConvertTo-Json -Compress), (New-Object Text.UTF8Encoding($false)))
|
||||
}
|
||||
finally {
|
||||
& logman.exe stop $SessionName -ets 2>$null | Out-Null
|
||||
[AasVerifier.JobProcess]::Close($jobProcess)
|
||||
Remove-Item -LiteralPath $etl,$csv,"$ResultOutput.stdout","$ResultOutput.stderr" -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace AasVerifier
|
||||
{
|
||||
public sealed class JobProcessHandles
|
||||
{
|
||||
public IntPtr JobHandle;
|
||||
public IntPtr ProcessHandle;
|
||||
public int ProcessId;
|
||||
}
|
||||
|
||||
public static class JobProcess
|
||||
{
|
||||
public const uint WaitObject0 = 0x00000000;
|
||||
public const uint WaitTimeout = 0x00000102;
|
||||
public const uint WaitFailed = 0xffffffff;
|
||||
|
||||
private const uint GenericRead = 0x80000000;
|
||||
private const uint GenericWrite = 0x40000000;
|
||||
private const uint FileShareRead = 0x00000001;
|
||||
private const uint FileShareWrite = 0x00000002;
|
||||
private const uint CreateAlways = 2;
|
||||
private const uint OpenExisting = 3;
|
||||
private const uint FileAttributeNormal = 0x00000080;
|
||||
private const uint CreateSuspended = 0x00000004;
|
||||
private const uint CreateNoWindow = 0x08000000;
|
||||
private const uint StartfUseStdHandles = 0x00000100;
|
||||
private const uint JobObjectLimitKillOnJobClose = 0x00002000;
|
||||
private const uint Synchronize = 0x00100000;
|
||||
private const int JobObjectBasicAccountingInformationClass = 1;
|
||||
private const int JobObjectExtendedLimitInformationClass = 9;
|
||||
private static readonly IntPtr InvalidHandleValue = new IntPtr(-1);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct SecurityAttributes
|
||||
{
|
||||
public int Length;
|
||||
public IntPtr SecurityDescriptor;
|
||||
[MarshalAs(UnmanagedType.Bool)] public bool InheritHandle;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private struct StartupInfo
|
||||
{
|
||||
public int Size;
|
||||
public string Reserved;
|
||||
public string Desktop;
|
||||
public string Title;
|
||||
public int X;
|
||||
public int Y;
|
||||
public int XSize;
|
||||
public int YSize;
|
||||
public int XCountChars;
|
||||
public int YCountChars;
|
||||
public int FillAttribute;
|
||||
public int Flags;
|
||||
public short ShowWindow;
|
||||
public short Reserved2;
|
||||
public IntPtr Reserved2Pointer;
|
||||
public IntPtr StandardInput;
|
||||
public IntPtr StandardOutput;
|
||||
public IntPtr StandardError;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct ProcessInformation
|
||||
{
|
||||
public IntPtr Process;
|
||||
public IntPtr Thread;
|
||||
public int ProcessId;
|
||||
public int ThreadId;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct JobObjectBasicLimitInformation
|
||||
{
|
||||
public long PerProcessUserTimeLimit;
|
||||
public long PerJobUserTimeLimit;
|
||||
public uint LimitFlags;
|
||||
public UIntPtr MinimumWorkingSetSize;
|
||||
public UIntPtr MaximumWorkingSetSize;
|
||||
public uint ActiveProcessLimit;
|
||||
public UIntPtr Affinity;
|
||||
public uint PriorityClass;
|
||||
public uint SchedulingClass;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct JobObjectBasicAccountingInformation
|
||||
{
|
||||
public long TotalUserTime;
|
||||
public long TotalKernelTime;
|
||||
public long ThisPeriodTotalUserTime;
|
||||
public long ThisPeriodTotalKernelTime;
|
||||
public uint TotalPageFaultCount;
|
||||
public uint TotalProcesses;
|
||||
public uint ActiveProcesses;
|
||||
public uint TotalTerminatedProcesses;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct IoCounters
|
||||
{
|
||||
public ulong ReadOperationCount;
|
||||
public ulong WriteOperationCount;
|
||||
public ulong OtherOperationCount;
|
||||
public ulong ReadTransferCount;
|
||||
public ulong WriteTransferCount;
|
||||
public ulong OtherTransferCount;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct JobObjectExtendedLimitInformation
|
||||
{
|
||||
public JobObjectBasicLimitInformation BasicLimitInformation;
|
||||
public IoCounters IoInfo;
|
||||
public UIntPtr ProcessMemoryLimit;
|
||||
public UIntPtr JobMemoryLimit;
|
||||
public UIntPtr PeakProcessMemoryUsed;
|
||||
public UIntPtr PeakJobMemoryUsed;
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern IntPtr CreateJobObjectW(IntPtr jobAttributes, string name);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool SetInformationJobObject(
|
||||
IntPtr job,
|
||||
int informationClass,
|
||||
ref JobObjectExtendedLimitInformation information,
|
||||
int informationLength);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool TerminateJobObject(IntPtr job, uint exitCode);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool QueryInformationJobObject(
|
||||
IntPtr job,
|
||||
int informationClass,
|
||||
ref JobObjectBasicAccountingInformation information,
|
||||
int informationLength,
|
||||
IntPtr returnLength);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool GetExitCodeProcess(IntPtr process, out uint exitCode);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern uint ResumeThread(IntPtr thread);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool TerminateProcess(IntPtr process, uint exitCode);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern IntPtr OpenProcess(uint desiredAccess, [MarshalAs(UnmanagedType.Bool)] bool inheritHandle, int processId);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern uint WaitForSingleObject(IntPtr handle, uint milliseconds);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool CloseHandle(IntPtr handle);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern IntPtr CreateFileW(
|
||||
string fileName,
|
||||
uint desiredAccess,
|
||||
uint shareMode,
|
||||
ref SecurityAttributes securityAttributes,
|
||||
uint creationDisposition,
|
||||
uint flagsAndAttributes,
|
||||
IntPtr templateFile);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool CreateProcessW(
|
||||
string applicationName,
|
||||
StringBuilder commandLine,
|
||||
IntPtr processAttributes,
|
||||
IntPtr threadAttributes,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool inheritHandles,
|
||||
uint creationFlags,
|
||||
IntPtr environment,
|
||||
string currentDirectory,
|
||||
ref StartupInfo startupInfo,
|
||||
out ProcessInformation processInformation);
|
||||
|
||||
private static void ThrowLastError(string operation)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(), operation);
|
||||
}
|
||||
|
||||
private static string QuoteArgument(string argument)
|
||||
{
|
||||
if (argument == null) throw new ArgumentNullException("argument");
|
||||
if (argument.Length > 0 && argument.IndexOfAny(new[] { ' ', '\t', '\n', '\v', '"' }) < 0) return argument;
|
||||
StringBuilder output = new StringBuilder();
|
||||
output.Append('"');
|
||||
int backslashes = 0;
|
||||
foreach (char character in argument)
|
||||
{
|
||||
if (character == '\\')
|
||||
{
|
||||
backslashes++;
|
||||
}
|
||||
else if (character == '"')
|
||||
{
|
||||
output.Append('\\', backslashes * 2 + 1);
|
||||
output.Append('"');
|
||||
backslashes = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
output.Append('\\', backslashes);
|
||||
output.Append(character);
|
||||
backslashes = 0;
|
||||
}
|
||||
}
|
||||
output.Append('\\', backslashes * 2);
|
||||
output.Append('"');
|
||||
return output.ToString();
|
||||
}
|
||||
|
||||
private static StringBuilder BuildCommandLine(string executable, string[] arguments)
|
||||
{
|
||||
List<string> tokens = new List<string>();
|
||||
tokens.Add(QuoteArgument(executable));
|
||||
foreach (string argument in arguments) tokens.Add(QuoteArgument(argument));
|
||||
return new StringBuilder(string.Join(" ", tokens.ToArray()));
|
||||
}
|
||||
|
||||
public static JobProcessHandles Start(
|
||||
string executable,
|
||||
string[] arguments,
|
||||
string stdoutPath,
|
||||
string stderrPath)
|
||||
{
|
||||
IntPtr job = IntPtr.Zero;
|
||||
IntPtr standardInput = InvalidHandleValue;
|
||||
IntPtr standardOutput = InvalidHandleValue;
|
||||
IntPtr standardError = InvalidHandleValue;
|
||||
ProcessInformation process = new ProcessInformation();
|
||||
bool processCreated = false;
|
||||
bool processAssigned = false;
|
||||
try
|
||||
{
|
||||
job = CreateJobObjectW(IntPtr.Zero, null);
|
||||
if (job == IntPtr.Zero) ThrowLastError("CreateJobObjectW failed");
|
||||
JobObjectExtendedLimitInformation limits = new JobObjectExtendedLimitInformation();
|
||||
limits.BasicLimitInformation.LimitFlags = JobObjectLimitKillOnJobClose;
|
||||
if (!SetInformationJobObject(
|
||||
job,
|
||||
JobObjectExtendedLimitInformationClass,
|
||||
ref limits,
|
||||
Marshal.SizeOf(typeof(JobObjectExtendedLimitInformation))))
|
||||
{
|
||||
ThrowLastError("SetInformationJobObject failed");
|
||||
}
|
||||
|
||||
SecurityAttributes inheritable = new SecurityAttributes();
|
||||
inheritable.Length = Marshal.SizeOf(typeof(SecurityAttributes));
|
||||
inheritable.InheritHandle = true;
|
||||
standardInput = CreateFileW("NUL", GenericRead, FileShareRead | FileShareWrite, ref inheritable, OpenExisting, FileAttributeNormal, IntPtr.Zero);
|
||||
standardOutput = CreateFileW(stdoutPath, GenericWrite, FileShareRead | FileShareWrite, ref inheritable, CreateAlways, FileAttributeNormal, IntPtr.Zero);
|
||||
standardError = CreateFileW(stderrPath, GenericWrite, FileShareRead | FileShareWrite, ref inheritable, CreateAlways, FileAttributeNormal, IntPtr.Zero);
|
||||
if (standardInput == InvalidHandleValue || standardOutput == InvalidHandleValue || standardError == InvalidHandleValue)
|
||||
{
|
||||
ThrowLastError("CreateFileW for redirected streams failed");
|
||||
}
|
||||
|
||||
StartupInfo startup = new StartupInfo();
|
||||
startup.Size = Marshal.SizeOf(typeof(StartupInfo));
|
||||
startup.Flags = (int)StartfUseStdHandles;
|
||||
startup.StandardInput = standardInput;
|
||||
startup.StandardOutput = standardOutput;
|
||||
startup.StandardError = standardError;
|
||||
if (!CreateProcessW(
|
||||
executable,
|
||||
BuildCommandLine(executable, arguments),
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
true,
|
||||
CreateSuspended | CreateNoWindow,
|
||||
IntPtr.Zero,
|
||||
null,
|
||||
ref startup,
|
||||
out process))
|
||||
{
|
||||
ThrowLastError("CreateProcessW failed");
|
||||
}
|
||||
processCreated = true;
|
||||
if (!AssignProcessToJobObject(job, process.Process)) ThrowLastError("AssignProcessToJobObject failed");
|
||||
processAssigned = true;
|
||||
if (ResumeThread(process.Thread) == WaitFailed) ThrowLastError("ResumeThread failed");
|
||||
CloseHandle(process.Thread);
|
||||
process.Thread = IntPtr.Zero;
|
||||
return new JobProcessHandles { JobHandle = job, ProcessHandle = process.Process, ProcessId = process.ProcessId };
|
||||
}
|
||||
catch (Exception startError)
|
||||
{
|
||||
Exception cleanupError = null;
|
||||
if (processCreated && !processAssigned && process.Process != IntPtr.Zero)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!TerminateProcess(process.Process, 125)) ThrowLastError("TerminateProcess for unassigned root failed");
|
||||
if (WaitForSingleObject(process.Process, 5000) != WaitObject0)
|
||||
{
|
||||
throw new InvalidOperationException("Unassigned suspended root did not terminate within the cleanup budget");
|
||||
}
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
cleanupError = error;
|
||||
}
|
||||
}
|
||||
if (job != IntPtr.Zero) CloseHandle(job);
|
||||
if (process.Thread != IntPtr.Zero) CloseHandle(process.Thread);
|
||||
if (process.Process != IntPtr.Zero) CloseHandle(process.Process);
|
||||
if (cleanupError != null)
|
||||
{
|
||||
throw new AggregateException("Candidate start failed and cleanup of the unassigned root also failed", startError, cleanupError);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (standardInput != InvalidHandleValue) CloseHandle(standardInput);
|
||||
if (standardOutput != InvalidHandleValue) CloseHandle(standardOutput);
|
||||
if (standardError != InvalidHandleValue) CloseHandle(standardError);
|
||||
}
|
||||
}
|
||||
|
||||
public static uint Wait(JobProcessHandles handles, int milliseconds)
|
||||
{
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
while (true)
|
||||
{
|
||||
JobObjectBasicAccountingInformation accounting = new JobObjectBasicAccountingInformation();
|
||||
if (!QueryInformationJobObject(
|
||||
handles.JobHandle,
|
||||
JobObjectBasicAccountingInformationClass,
|
||||
ref accounting,
|
||||
Marshal.SizeOf(typeof(JobObjectBasicAccountingInformation)),
|
||||
IntPtr.Zero))
|
||||
{
|
||||
ThrowLastError("QueryInformationJobObject failed");
|
||||
}
|
||||
if (accounting.ActiveProcesses == 0) return WaitObject0;
|
||||
if (stopwatch.ElapsedMilliseconds >= milliseconds) return WaitTimeout;
|
||||
Thread.Sleep(Math.Min(25, Math.Max(1, milliseconds - (int)stopwatch.ElapsedMilliseconds)));
|
||||
}
|
||||
}
|
||||
|
||||
public static uint TotalProcesses(JobProcessHandles handles)
|
||||
{
|
||||
JobObjectBasicAccountingInformation accounting = new JobObjectBasicAccountingInformation();
|
||||
if (!QueryInformationJobObject(
|
||||
handles.JobHandle,
|
||||
JobObjectBasicAccountingInformationClass,
|
||||
ref accounting,
|
||||
Marshal.SizeOf(typeof(JobObjectBasicAccountingInformation)),
|
||||
IntPtr.Zero))
|
||||
{
|
||||
ThrowLastError("QueryInformationJobObject failed");
|
||||
}
|
||||
return accounting.TotalProcesses;
|
||||
}
|
||||
|
||||
public static void Terminate(JobProcessHandles handles, uint exitCode)
|
||||
{
|
||||
if (!TerminateJobObject(handles.JobHandle, exitCode)) ThrowLastError("TerminateJobObject failed");
|
||||
}
|
||||
|
||||
public static uint ExitCode(JobProcessHandles handles)
|
||||
{
|
||||
uint exitCode;
|
||||
if (!GetExitCodeProcess(handles.ProcessHandle, out exitCode)) ThrowLastError("GetExitCodeProcess failed");
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
public static bool WaitForProcessExit(int processId, int milliseconds)
|
||||
{
|
||||
if (processId <= 0) throw new ArgumentOutOfRangeException("processId");
|
||||
if (milliseconds < 1 || milliseconds > 900000) throw new ArgumentOutOfRangeException("milliseconds");
|
||||
IntPtr process = OpenProcess(Synchronize, false, processId);
|
||||
if (process == IntPtr.Zero) ThrowLastError("OpenProcess for parent synchronization failed");
|
||||
try
|
||||
{
|
||||
uint result = WaitForSingleObject(process, (uint)milliseconds);
|
||||
if (result == WaitObject0) return true;
|
||||
if (result == WaitTimeout) return false;
|
||||
ThrowLastError("WaitForSingleObject for parent synchronization failed");
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseHandle(process);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Close(JobProcessHandles handles)
|
||||
{
|
||||
if (handles == null) return;
|
||||
if (handles.JobHandle != IntPtr.Zero)
|
||||
{
|
||||
CloseHandle(handles.JobHandle);
|
||||
handles.JobHandle = IntPtr.Zero;
|
||||
}
|
||||
if (handles.ProcessHandle != IntPtr.Zero)
|
||||
{
|
||||
CloseHandle(handles.ProcessHandle);
|
||||
handles.ProcessHandle = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user