зеркало из https://github.com/microsoft/BuildXL.git
Merged PR 682705: Enable nullable for SandboxedProcessInfo, SandboxedProcessFactory
This commit is contained in:
Родитель
3226a45ce1
Коммит
c6ecf6f11c
|
@ -91,7 +91,11 @@ namespace BuildXL.Processes.Remoting
|
|||
WorkingDirectory = m_processInfo!.WorkingDirectory,
|
||||
};
|
||||
|
||||
remoteData.EnvironmentVariables.AddRange(m_processInfo!.EnvironmentVariables.ToDictionary());
|
||||
if (m_processInfo!.EnvironmentVariables != null)
|
||||
{
|
||||
remoteData.EnvironmentVariables.AddRange(m_processInfo!.EnvironmentVariables.ToDictionary());
|
||||
}
|
||||
|
||||
remoteData.FileDependencies.AddRange(ToStringPaths(m_fileDependencies));
|
||||
remoteData.DirectoryDependencies.AddRange(ToStringPaths(m_directoryDependencies));
|
||||
remoteData.OutputDirectories.AddRange(ToStringPaths(m_outputDirectories));
|
||||
|
|
|
@ -54,7 +54,7 @@ namespace BuildXL.Processes
|
|||
|
||||
private readonly PooledObjectWrapper<MemoryStream> m_fileAccessManifestStreamWrapper;
|
||||
private MemoryStream FileAccessManifestStream => m_fileAccessManifestStreamWrapper.Instance;
|
||||
private FileAccessManifest? m_fileAccessManifest;
|
||||
private FileAccessManifest m_fileAccessManifest;
|
||||
|
||||
private readonly TaskSourceSlim<SandboxedProcessResult> m_resultTaskCompletionSource =
|
||||
TaskSourceSlim.Create<SandboxedProcessResult>();
|
||||
|
@ -67,7 +67,7 @@ namespace BuildXL.Processes
|
|||
private SandboxedProcessOutputBuilder m_error;
|
||||
private SandboxedProcessOutputBuilder m_output;
|
||||
private readonly SandboxedProcessTraceBuilder? m_traceBuilder;
|
||||
private SandboxedProcessReports? m_reports;
|
||||
private SandboxedProcessReports m_reports;
|
||||
private IAsyncPipeReader? m_reportReader;
|
||||
private readonly SemaphoreSlim m_reportReaderSemaphore = TaskUtilities.CreateMutex();
|
||||
private Dictionary<uint, ReportedProcess>? m_survivingChildProcesses;
|
||||
|
@ -89,7 +89,6 @@ namespace BuildXL.Processes
|
|||
{
|
||||
Contract.Requires(!info.Timeout.HasValue || info.Timeout.Value <= Process.MaxTimeout);
|
||||
Contract.Requires(!info.CreateSandboxTraceFile
|
||||
|| info.FileAccessManifest == null
|
||||
|| (info.FileAccessManifest.ReportFileAccesses && info.FileAccessManifest.LogProcessData && info.FileAccessManifest.ReportProcessArgs),
|
||||
"Trace file is enabled, but some of the required options in the file access manifest are not.");
|
||||
|
||||
|
@ -139,8 +138,7 @@ namespace BuildXL.Processes
|
|||
? new SandboxedProcessTraceBuilder(info.FileStorage, info.PathTable)
|
||||
: null;
|
||||
|
||||
m_reports = m_fileAccessManifest != null ?
|
||||
new SandboxedProcessReports(
|
||||
m_reports = new SandboxedProcessReports(
|
||||
m_fileAccessManifest,
|
||||
info.PathTable,
|
||||
info.PipSemiStableHash,
|
||||
|
@ -149,7 +147,7 @@ namespace BuildXL.Processes
|
|||
info.DetoursEventListener,
|
||||
info.SidebandWriter,
|
||||
info.FileSystemView,
|
||||
m_traceBuilder) : null;
|
||||
m_traceBuilder);
|
||||
|
||||
m_detouredProcess =
|
||||
new DetouredProcess(
|
||||
|
@ -171,7 +169,7 @@ namespace BuildXL.Processes
|
|||
info.ContainerConfiguration,
|
||||
// If there is any process configured to breakway from the sandbox, then we need to allow
|
||||
// this to happen at the job object level
|
||||
setJobBreakawayOk: m_fileAccessManifest?.ProcessesCanBreakaway ?? false,
|
||||
setJobBreakawayOk: m_fileAccessManifest.ProcessesCanBreakaway,
|
||||
info.CreateJobObjectForCurrentProcess,
|
||||
info.DiagnosticsEnabled,
|
||||
m_numRetriesPipeReadOnCancel,
|
||||
|
@ -187,11 +185,6 @@ namespace BuildXL.Processes
|
|||
/// <inheritdoc />
|
||||
public int GetLastMessageCount()
|
||||
{
|
||||
if (m_reports == null)
|
||||
{
|
||||
return 0; // We didn't count the messages.
|
||||
}
|
||||
|
||||
return m_reports.GetLastMessageCount();
|
||||
}
|
||||
|
||||
|
@ -392,12 +385,7 @@ namespace BuildXL.Processes
|
|||
/// <inheritdoc />
|
||||
public long GetDetoursMaxHeapSize()
|
||||
{
|
||||
if (m_reports != null)
|
||||
{
|
||||
return m_reports.MaxDetoursHeapSize;
|
||||
}
|
||||
|
||||
return 0L;
|
||||
return m_reports.MaxDetoursHeapSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -426,8 +414,6 @@ namespace BuildXL.Processes
|
|||
m_output.Dispose();
|
||||
m_error.Dispose();
|
||||
|
||||
m_reports = null;
|
||||
|
||||
m_fileAccessManifestStreamWrapper.Dispose();
|
||||
}
|
||||
|
||||
|
@ -505,11 +491,7 @@ namespace BuildXL.Processes
|
|||
|
||||
bool debugFlagsMatch = true;
|
||||
ArraySegment<byte> manifestBytes = new ArraySegment<byte>();
|
||||
if (m_fileAccessManifest != null)
|
||||
{
|
||||
manifestBytes = m_fileAccessManifest.GetPayloadBytes(m_loggingContext, setup, FileAccessManifestStream, m_timeoutMins, ref debugFlagsMatch);
|
||||
}
|
||||
|
||||
manifestBytes = m_fileAccessManifest.GetPayloadBytes(m_loggingContext, setup, FileAccessManifestStream, m_timeoutMins, ref debugFlagsMatch);
|
||||
if (!debugFlagsMatch)
|
||||
{
|
||||
throw new BuildXLException("Mismatching build type for BuildXL and DetoursServices.dll.");
|
||||
|
@ -544,14 +526,11 @@ namespace BuildXL.Processes
|
|||
}
|
||||
|
||||
string memUsage = $"RamPercent: {ramPercent}, AvailableRamMb: {availableRamMb}, AvailablePageFileMb: {availablePageFileMb}, TotalPageFileMb: {totalPageFileMb}";
|
||||
Native.Tracing.Logger.Log.DetouredProcessAccessViolationException(m_loggingContext, (m_reports?.PipDescription ?? "") + " - " + memUsage);
|
||||
Native.Tracing.Logger.Log.DetouredProcessAccessViolationException(m_loggingContext, m_reports.PipDescription + " - " + memUsage);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// release memory
|
||||
m_fileAccessManifest = null;
|
||||
|
||||
// Note that in the success path, childHandle should already be closed (by Start).
|
||||
if (childHandle != null && !childHandle.IsInvalid)
|
||||
{
|
||||
|
@ -559,13 +538,13 @@ namespace BuildXL.Processes
|
|||
}
|
||||
}
|
||||
|
||||
StreamDataReceived? reportLineReceivedCallback = m_reports == null ? null : ReportLineReceived;
|
||||
StreamDataReceived reportLineReceivedCallback = ReportLineReceived;
|
||||
|
||||
if (useManagedPipeReader)
|
||||
{
|
||||
m_reportReader = PipeReaderFactory.CreateManagedPipeReader(
|
||||
pipeStream,
|
||||
message => reportLineReceivedCallback?.Invoke(message) ?? true,
|
||||
message => reportLineReceivedCallback(message),
|
||||
reportEncoding,
|
||||
m_bufferSize);
|
||||
}
|
||||
|
@ -597,11 +576,11 @@ namespace BuildXL.Processes
|
|||
SandboxedProcessFactory.Counters.IncrementCounter(SandboxedProcessFactory.SandboxedProcessCounters.AccessReportCount);
|
||||
using (SandboxedProcessFactory.Counters.StartStopwatch(SandboxedProcessFactory.SandboxedProcessCounters.HandleAccessReportDuration))
|
||||
{
|
||||
return m_reports!.ReportLineReceived(data);
|
||||
return m_reports.ReportLineReceived(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void DebugPipeConnection(string data) => m_reports?.ReportLineReceived($"{(int)ReportType.DebugMessage},{data}");
|
||||
private void DebugPipeConnection(string data) => m_reports.ReportLineReceived($"{(int)ReportType.DebugMessage},{data}");
|
||||
|
||||
private static async Task FeedStandardInputAsync(DetouredProcess detouredProcess, TextReader? reader, TaskSourceSlim<bool> stdInTcs)
|
||||
{
|
||||
|
@ -672,7 +651,7 @@ namespace BuildXL.Processes
|
|||
await WaitUntilReportEof(m_detouredProcess!.Killed);
|
||||
|
||||
// Ensure no further modifications to the report
|
||||
m_reports?.Freeze();
|
||||
m_reports.Freeze();
|
||||
|
||||
// We can get extended accounting information (peak memory, etc. rolled up for the entire process tree) if this process was wrapped in a job.
|
||||
JobObject.AccountingInformation? jobAccountingInformation = null;
|
||||
|
@ -713,7 +692,7 @@ namespace BuildXL.Processes
|
|||
// Construct result; note that the process is expected to have exited at this point, even if we decided to forcefully kill it
|
||||
// (this callback is always a result of the process handle being signaled).
|
||||
int exitCode = 0;
|
||||
if (m_reports?.MessageProcessingFailure != null)
|
||||
if (m_reports.MessageProcessingFailure != null)
|
||||
{
|
||||
exitCode = ExitCodes.MessageProcessingFailure;
|
||||
}
|
||||
|
@ -737,17 +716,17 @@ namespace BuildXL.Processes
|
|||
StandardOutput = m_output.Freeze(),
|
||||
StandardError = m_error.Freeze(),
|
||||
TraceFile = m_traceBuilder?.Freeze(),
|
||||
AllUnexpectedFileAccesses = m_reports?.FileUnexpectedAccesses,
|
||||
FileAccesses = m_reports?.FileAccesses,
|
||||
DetouringStatuses = m_reports?.ProcessDetoursStatuses,
|
||||
ExplicitlyReportedFileAccesses = m_reports?.ExplicitlyReportedFileAccesses,
|
||||
Processes = m_reports?.Processes,
|
||||
AllUnexpectedFileAccesses = m_reports.FileUnexpectedAccesses,
|
||||
FileAccesses = m_reports.FileAccesses,
|
||||
DetouringStatuses = m_reports.ProcessDetoursStatuses,
|
||||
ExplicitlyReportedFileAccesses = m_reports.ExplicitlyReportedFileAccesses,
|
||||
Processes = m_reports.Processes,
|
||||
DumpFileDirectory = m_detouredProcess.DumpFileDirectory,
|
||||
DumpCreationException = m_detouredProcess.DumpCreationException,
|
||||
StandardInputException = standardInputException,
|
||||
MessageProcessingFailure = m_reports?.MessageProcessingFailure,
|
||||
MessageProcessingFailure = m_reports.MessageProcessingFailure,
|
||||
ProcessStartTime = m_detouredProcess.StartTime,
|
||||
HasReadWriteToReadFileAccessRequest = m_reports?.HasReadWriteToReadFileAccessRequest ?? false,
|
||||
HasReadWriteToReadFileAccessRequest = m_reports.HasReadWriteToReadFileAccessRequest,
|
||||
DiagnosticMessage = m_detouredProcess.Diagnostics
|
||||
};
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ using BuildXL.Utilities.Instrumentation.Common;
|
|||
using BuildXL.Utilities.Tracing;
|
||||
using static BuildXL.Tracing.Diagnostics;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace BuildXL.Processes
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -224,7 +226,7 @@ namespace BuildXL.Processes
|
|||
public static readonly CounterCollection<SandboxedProcessCounters> Counters = new ();
|
||||
|
||||
/// <summary>
|
||||
/// Start a sand-boxed process asynchronously. The result will only be available once the process terminates.
|
||||
/// Start a sandboxed process asynchronously. The result will only be available once the process terminates.
|
||||
/// </summary>
|
||||
/// <exception cref="BuildXLException">
|
||||
/// Thrown if the process creation fails in a recoverable manner due do some obscure problem detected by the underlying
|
||||
|
@ -233,12 +235,13 @@ namespace BuildXL.Processes
|
|||
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Object lives on via task result.")]
|
||||
public static Task<ISandboxedProcess> StartAsync(SandboxedProcessInfo info, bool forceSandboxing)
|
||||
{
|
||||
Contract.Requires(info != null);
|
||||
Contract.Requires(info.FileName != null);
|
||||
Contract.Requires(info.LoggingContext != null);
|
||||
Contract.Requires(
|
||||
info.GetCommandLine().Length <= SandboxedProcessInfo.MaxCommandLineLength,
|
||||
$"Command line's length ({info.GetCommandLine().Length}) exceeds the max length {SandboxedProcessInfo.MaxCommandLineLength}");
|
||||
string cmdLine = info.GetCommandLine();
|
||||
if (cmdLine.Length > SandboxedProcessInfo.MaxCommandLineLength)
|
||||
{
|
||||
Contract.Requires(
|
||||
cmdLine.Length <= SandboxedProcessInfo.MaxCommandLineLength,
|
||||
$"Command line's length ({cmdLine.Length}) exceeds the max length {SandboxedProcessInfo.MaxCommandLineLength}");
|
||||
}
|
||||
|
||||
if (info.TestRetries)
|
||||
{
|
||||
|
@ -284,12 +287,12 @@ namespace BuildXL.Processes
|
|||
/// This is a separate function and not inlined as an anonymous delegate, as VS seems to have trouble with those when
|
||||
/// measuring code coverage
|
||||
/// </remarks>
|
||||
private static ISandboxedProcess ProcessStart(object state)
|
||||
private static ISandboxedProcess ProcessStart(object? state)
|
||||
{
|
||||
Counters.IncrementCounter(SandboxedProcessCounters.SandboxedProcessCount);
|
||||
var stateTuple = (Tuple<SandboxedProcessInfo, bool>)state;
|
||||
var stateTuple = (Tuple<SandboxedProcessInfo, bool>)state!;
|
||||
SandboxedProcessInfo info = stateTuple.Item1;
|
||||
ISandboxedProcess result = null;
|
||||
ISandboxedProcess? result = null;
|
||||
try
|
||||
{
|
||||
result = Create(info, forceSandboxing: stateTuple.Item2);
|
||||
|
|
|
@ -17,6 +17,8 @@ using CanBeNullAttribute = JetBrains.Annotations.CanBeNullAttribute;
|
|||
using static BuildXL.Utilities.BuildParameters;
|
||||
using BuildXL.Processes.Remoting;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace BuildXL.Processes
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -62,13 +64,13 @@ namespace BuildXL.Processes
|
|||
/// </remarks>
|
||||
public static readonly TimeSpan DefaultNestedProcessTerminationTimeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
private string m_arguments;
|
||||
private string? m_arguments;
|
||||
|
||||
private string m_commandLine;
|
||||
private string? m_commandLine;
|
||||
|
||||
private byte[] m_environmentBlock;
|
||||
private byte[]? m_environmentBlock;
|
||||
|
||||
private string m_rootMappingBlock;
|
||||
private string? m_rootMappingBlock;
|
||||
|
||||
private int m_maxLengthInMemory = 16384;
|
||||
|
||||
|
@ -80,22 +82,22 @@ namespace BuildXL.Processes
|
|||
/// <summary>
|
||||
/// A detours event listener.
|
||||
/// </summary>
|
||||
public IDetoursEventListener DetoursEventListener { get; private set; }
|
||||
public IDetoursEventListener? DetoursEventListener { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// A macOS kernel extension connection.
|
||||
/// </summary>
|
||||
public ISandboxConnection SandboxConnection;
|
||||
public ISandboxConnection? SandboxConnection;
|
||||
|
||||
/// <summary>
|
||||
/// An optional shared opaque output logger to use to record file writes under shared opaque directories as soon as they happen.
|
||||
/// </summary>
|
||||
public SidebandWriter SidebandWriter { get; }
|
||||
public SidebandWriter? SidebandWriter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional file system view to report outputs as soon as they are produced
|
||||
/// </summary>
|
||||
public ISandboxFileSystemView FileSystemView { get; }
|
||||
public ISandboxFileSystemView? FileSystemView { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the process creating a <see cref="SandboxedProcess"/> gets added to a job object
|
||||
|
@ -113,7 +115,7 @@ namespace BuildXL.Processes
|
|||
/// <summary>
|
||||
/// Indicates whether resource usage of the sandboxed process tree should be measured and logged for later inspection.
|
||||
/// </summary>
|
||||
public SandboxedProcessResourceMonitoringConfig MonitoringConfig;
|
||||
public SandboxedProcessResourceMonitoringConfig? MonitoringConfig { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the path remapping information for a process that needs to run in a container
|
||||
|
@ -131,15 +133,15 @@ namespace BuildXL.Processes
|
|||
/// compile against this assembly and already depend on this constructor.
|
||||
/// </remarks>
|
||||
public SandboxedProcessInfo(
|
||||
[CanBeNull] ISandboxedProcessFileStorage fileStorage,
|
||||
ISandboxedProcessFileStorage? fileStorage,
|
||||
string fileName,
|
||||
bool disableConHostSharing,
|
||||
bool testRetries = false,
|
||||
LoggingContext loggingContext = null,
|
||||
IDetoursEventListener detoursEventListener = null,
|
||||
ISandboxConnection sandboxConnection = null,
|
||||
LoggingContext? loggingContext = null,
|
||||
IDetoursEventListener? detoursEventListener = null,
|
||||
ISandboxConnection? sandboxConnection = null,
|
||||
bool createJobObjectForCurrentProcess = true,
|
||||
SandboxedProcessResourceMonitoringConfig monitoringConfig = null)
|
||||
SandboxedProcessResourceMonitoringConfig? monitoringConfig = null)
|
||||
: this(
|
||||
new PathTable(),
|
||||
fileStorage,
|
||||
|
@ -159,25 +161,22 @@ namespace BuildXL.Processes
|
|||
/// </summary>
|
||||
public SandboxedProcessInfo(
|
||||
PathTable pathTable,
|
||||
[CanBeNull] ISandboxedProcessFileStorage fileStorage,
|
||||
ISandboxedProcessFileStorage? fileStorage,
|
||||
string fileName,
|
||||
FileAccessManifest fileAccessManifest,
|
||||
FileAccessManifest? fileAccessManifest,
|
||||
bool disableConHostSharing,
|
||||
ContainerConfiguration containerConfiguration,
|
||||
LoggingContext loggingContext,
|
||||
bool testRetries = false,
|
||||
IDetoursEventListener detoursEventListener = null,
|
||||
ISandboxConnection sandboxConnection = null,
|
||||
SidebandWriter sidebandWriter = null,
|
||||
IDetoursEventListener? detoursEventListener = null,
|
||||
ISandboxConnection? sandboxConnection = null,
|
||||
SidebandWriter? sidebandWriter = null,
|
||||
bool createJobObjectForCurrentProcess = true,
|
||||
ISandboxFileSystemView fileSystemView = null,
|
||||
SandboxedProcessResourceMonitoringConfig monitoringConfig = null)
|
||||
ISandboxFileSystemView? fileSystemView = null,
|
||||
SandboxedProcessResourceMonitoringConfig? monitoringConfig = null)
|
||||
{
|
||||
Contract.RequiresNotNull(pathTable);
|
||||
Contract.RequiresNotNull(fileName);
|
||||
|
||||
PathTable = pathTable;
|
||||
FileAccessManifest = fileAccessManifest;
|
||||
FileAccessManifest = fileAccessManifest ?? new FileAccessManifest(pathTable);
|
||||
FileStorage = fileStorage;
|
||||
FileName = fileName;
|
||||
DisableConHostSharing = disableConHostSharing;
|
||||
|
@ -201,23 +200,23 @@ namespace BuildXL.Processes
|
|||
/// </summary>
|
||||
public SandboxedProcessInfo(
|
||||
PathTable pathTable,
|
||||
[CanBeNull] ISandboxedProcessFileStorage fileStorage,
|
||||
ISandboxedProcessFileStorage? fileStorage,
|
||||
string fileName,
|
||||
bool disableConHostSharing,
|
||||
LoggingContext loggingContext,
|
||||
bool testRetries = false,
|
||||
IDetoursEventListener detoursEventListener = null,
|
||||
ISandboxConnection sandboxConnection = null,
|
||||
ContainerConfiguration containerConfiguration = null,
|
||||
FileAccessManifest fileAccessManifest = null,
|
||||
IDetoursEventListener? detoursEventListener = null,
|
||||
ISandboxConnection? sandboxConnection = null,
|
||||
ContainerConfiguration? containerConfiguration = null,
|
||||
FileAccessManifest? fileAccessManifest = null,
|
||||
bool createJobObjectForCurrentProcess = true,
|
||||
SandboxedProcessResourceMonitoringConfig monitoringConfig = null
|
||||
SandboxedProcessResourceMonitoringConfig? monitoringConfig = null
|
||||
)
|
||||
: this(
|
||||
pathTable,
|
||||
fileStorage,
|
||||
fileName,
|
||||
fileAccessManifest ?? new FileAccessManifest(pathTable),
|
||||
fileAccessManifest,
|
||||
disableConHostSharing,
|
||||
containerConfiguration ?? ContainerConfiguration.DisabledIsolation,
|
||||
loggingContext,
|
||||
|
@ -227,8 +226,6 @@ namespace BuildXL.Processes
|
|||
createJobObjectForCurrentProcess: createJobObjectForCurrentProcess,
|
||||
monitoringConfig: monitoringConfig)
|
||||
{
|
||||
Contract.RequiresNotNull(pathTable);
|
||||
Contract.RequiresNotNull(fileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -250,7 +247,7 @@ namespace BuildXL.Processes
|
|||
/// <summary>
|
||||
/// Optional file storage options for stdout and stderr output streams.
|
||||
/// </summary>
|
||||
public ISandboxedProcessFileStorage FileStorage { get; }
|
||||
public ISandboxedProcessFileStorage? FileStorage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// When stdout or stderr are redirected and this flag is true, disables sharing of the instance of
|
||||
|
@ -281,17 +278,17 @@ namespace BuildXL.Processes
|
|||
/// <summary>
|
||||
/// How to decode the standard input; if not set, encoding of current process is used
|
||||
/// </summary>
|
||||
public Encoding StandardInputEncoding { get; set; }
|
||||
public Encoding? StandardInputEncoding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How to decode the standard output; if not set, encoding of current process is used
|
||||
/// </summary>
|
||||
public Encoding StandardErrorEncoding { get; set; }
|
||||
public Encoding? StandardErrorEncoding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How to decode the standard output; if not set, encoding of current process is used
|
||||
/// </summary>
|
||||
public Encoding StandardOutputEncoding { get; set; }
|
||||
public Encoding? StandardOutputEncoding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of pipe reading retries on cancellation.
|
||||
|
@ -310,7 +307,7 @@ namespace BuildXL.Processes
|
|||
/// <summary>
|
||||
/// Encoded command line arguments
|
||||
/// </summary>
|
||||
public string Arguments
|
||||
public string? Arguments
|
||||
{
|
||||
get
|
||||
{
|
||||
|
@ -327,7 +324,7 @@ namespace BuildXL.Processes
|
|||
/// <summary>
|
||||
/// Working directory (can be null)
|
||||
/// </summary>
|
||||
public string WorkingDirectory { get; set; }
|
||||
public string? WorkingDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Root jail information (can be null)
|
||||
|
@ -340,37 +337,37 @@ namespace BuildXL.Processes
|
|||
/// <summary>
|
||||
/// Environment variables (can be null)
|
||||
/// </summary>
|
||||
public IBuildParameters EnvironmentVariables { get; set; }
|
||||
public IBuildParameters? EnvironmentVariables { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Root drive remappings (can be null)
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, string> RootMappings { get; set; }
|
||||
public IReadOnlyDictionary<string, string>? RootMappings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional standard input stream from which to read
|
||||
/// </summary>
|
||||
public TextReader StandardInputReader { get; set; }
|
||||
public TextReader? StandardInputReader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional observer of each output line
|
||||
/// </summary>
|
||||
public Action<string> StandardOutputObserver { get; set; }
|
||||
public Action<string>? StandardOutputObserver { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional observer of each output line
|
||||
/// </summary>
|
||||
public Action<string> StandardErrorObserver { get; set; }
|
||||
public Action<string>? StandardErrorObserver { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Allowed surviving child processes.
|
||||
/// </summary>
|
||||
public string[] AllowedSurvivingChildProcessNames { get; set; }
|
||||
public string[]? AllowedSurvivingChildProcessNames { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Temp folder redirection.
|
||||
/// </summary>
|
||||
public (string source, string target)[] RedirectedTempFolders { get; set; }
|
||||
public (string source, string target)[]? RedirectedTempFolders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Max. number of characters buffered in memory before output is streamed to disk
|
||||
|
@ -397,7 +394,7 @@ namespace BuildXL.Processes
|
|||
/// <summary>
|
||||
/// File where Detours log failure message, e.g., communication failure, injection failure, etc.
|
||||
/// </summary>
|
||||
public string DetoursFailureFile { get; set; }
|
||||
public string? DetoursFailureFile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command line, comprised of the executable file name and the arguments.
|
||||
|
@ -414,7 +411,7 @@ namespace BuildXL.Processes
|
|||
/// </summary>
|
||||
public string GetUnicodeRootMappingBlock()
|
||||
{
|
||||
IReadOnlyDictionary<string, string> rootMappings = RootMappings;
|
||||
IReadOnlyDictionary<string, string>? rootMappings = RootMappings;
|
||||
if (rootMappings == null)
|
||||
{
|
||||
return string.Empty;
|
||||
|
@ -445,7 +442,7 @@ namespace BuildXL.Processes
|
|||
/// <summary>
|
||||
/// Gets the current environment variables, if any, as a unicode environment block
|
||||
/// </summary>
|
||||
public byte[] GetUnicodeEnvironmentBlock()
|
||||
public byte[]? GetUnicodeEnvironmentBlock()
|
||||
{
|
||||
return m_environmentBlock ??= ProcessUtilities.SerializeEnvironmentBlock(EnvironmentVariables?.ToDictionary());
|
||||
}
|
||||
|
@ -472,12 +469,12 @@ namespace BuildXL.Processes
|
|||
/// Root directory where timeout dumps for the process should be stored. This directory may contain other outputs
|
||||
/// for the process.
|
||||
/// </summary>
|
||||
public string TimeoutDumpDirectory { get; set; }
|
||||
public string? TimeoutDumpDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Root directory where surviving child process dumps should be saved
|
||||
/// </summary>
|
||||
public string SurvivingPipProcessChildrenDumpDirectory { get; set; }
|
||||
public string? SurvivingPipProcessChildrenDumpDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The kind of sandboxing to use.
|
||||
|
@ -487,7 +484,7 @@ namespace BuildXL.Processes
|
|||
/// <summary>
|
||||
/// Pip's Description. Used for logging.
|
||||
/// </summary>
|
||||
public string PipDescription { get; set; }
|
||||
public string? PipDescription { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Standard output and error options for the sandboxed process.
|
||||
|
@ -495,7 +492,7 @@ namespace BuildXL.Processes
|
|||
/// <remarks>
|
||||
/// This instance of <see cref="SandboxedProcessStandardFiles"/> is used as an alternative to <see cref="FileStorage"/>.
|
||||
/// </remarks>
|
||||
public SandboxedProcessStandardFiles SandboxedProcessStandardFiles { get; set; }
|
||||
public SandboxedProcessStandardFiles? SandboxedProcessStandardFiles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Info about the source of standard input.
|
||||
|
@ -503,7 +500,7 @@ namespace BuildXL.Processes
|
|||
/// <remarks>
|
||||
/// This instance of <see cref="StandardInputInfo"/> is used as a serialized version of <see cref="StandardInputReader"/>.
|
||||
/// </remarks>
|
||||
public StandardInputInfo StandardInputSourceInfo { get; set; }
|
||||
public StandardInputInfo? StandardInputSourceInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Observer descriptor.
|
||||
|
@ -511,7 +508,7 @@ namespace BuildXL.Processes
|
|||
/// <remarks>
|
||||
/// This instance of <see cref="SandboxObserverDescriptor"/> is used as a serialized version of <see cref="StandardOutputObserver"/> and <see cref="StandardErrorObserver"/>.
|
||||
/// </remarks>
|
||||
public SandboxObserverDescriptor StandardObserverDescriptor { get; set; }
|
||||
public SandboxObserverDescriptor? StandardObserverDescriptor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Provenance description.
|
||||
|
@ -526,13 +523,19 @@ namespace BuildXL.Processes
|
|||
/// <summary>
|
||||
/// Extra data for executing a pip in an external VM.
|
||||
/// </summary>
|
||||
public ExternalVMSandboxedProcessData ExternalVMSandboxedProcessData { get; set; }
|
||||
public ExternalVMSandboxedProcessData? ExternalVMSandboxedProcessData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to create a sandbox trace file.
|
||||
/// </summary>
|
||||
public bool CreateSandboxTraceFile { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional existing Windows job object handle to use for this process.
|
||||
/// The job object handle will not be closed automatically and must be closed by the caller.
|
||||
/// </summary>
|
||||
public IntPtr PreExistingJobObjectOrZero { get; init; }
|
||||
|
||||
#region Serialization
|
||||
|
||||
/// <nodoc />
|
||||
|
@ -552,7 +555,7 @@ namespace BuildXL.Processes
|
|||
writer.Write(
|
||||
EnvironmentVariables,
|
||||
(w, v) => w.WriteReadOnlyList(
|
||||
v.ToDictionary().ToList(),
|
||||
v!.ToDictionary().ToList(),
|
||||
(w2, kvp) =>
|
||||
{
|
||||
w2.Write(kvp.Key);
|
||||
|
@ -586,17 +589,17 @@ namespace BuildXL.Processes
|
|||
SandboxedProcessStandardFiles.Serialize(writer);
|
||||
}
|
||||
|
||||
writer.Write(StandardInputSourceInfo, (w, v) => v.Serialize(w));
|
||||
writer.Write(StandardObserverDescriptor, (w, v) => v.Serialize(w));
|
||||
writer.Write(StandardInputSourceInfo, (w, v) => v!.Serialize(w));
|
||||
writer.Write(StandardObserverDescriptor, (w, v) => v!.Serialize(w));
|
||||
writer.Write(NumRetriesPipeReadOnCancel);
|
||||
writer.Write(
|
||||
RedirectedTempFolders,
|
||||
(w, v) => w.WriteReadOnlyList(v, (w2, v2) => { w2.Write(v2.source); w2.Write(v2.target); }));
|
||||
|
||||
writer.Write(SidebandWriter, (w, v) => v.Serialize(w));
|
||||
writer.Write(SidebandWriter, (w, v) => v!.Serialize(w));
|
||||
writer.Write(CreateJobObjectForCurrentProcess);
|
||||
writer.WriteNullableString(DetoursFailureFile);
|
||||
writer.Write(ExternalVMSandboxedProcessData, (w, v) => v.Serialize(w));
|
||||
writer.Write(ExternalVMSandboxedProcessData, (w, v) => v!.Serialize(w));
|
||||
writer.Write(CreateSandboxTraceFile);
|
||||
|
||||
// File access manifest should be serialized the last.
|
||||
|
@ -605,41 +608,41 @@ namespace BuildXL.Processes
|
|||
}
|
||||
|
||||
/// <nodoc />
|
||||
public static SandboxedProcessInfo Deserialize(Stream stream, LoggingContext loggingContext, IDetoursEventListener detoursEventListener)
|
||||
public static SandboxedProcessInfo Deserialize(Stream stream, LoggingContext loggingContext, IDetoursEventListener? detoursEventListener)
|
||||
{
|
||||
using (var reader = new BuildXLReader(false, stream, true))
|
||||
{
|
||||
string arguments = reader.ReadNullableString();
|
||||
string commandLine = reader.ReadNullableString();
|
||||
string? arguments = reader.ReadNullableString();
|
||||
string? commandLine = reader.ReadNullableString();
|
||||
bool disableConHostSharing = reader.ReadBoolean();
|
||||
string fileName = reader.ReadNullableString();
|
||||
Encoding standardInputEncoding = reader.ReadNullable(r => r.ReadEncoding());
|
||||
Encoding standardOutputEncoding = reader.ReadNullable(r => r.ReadEncoding());
|
||||
Encoding standardErrorEncoding = reader.ReadNullable(r => r.ReadEncoding());
|
||||
string workingDirectory = reader.ReadNullableString();
|
||||
string? fileName = reader.ReadNullableString();
|
||||
Encoding? standardInputEncoding = reader.ReadNullable(r => r.ReadEncoding());
|
||||
Encoding? standardOutputEncoding = reader.ReadNullable(r => r.ReadEncoding());
|
||||
Encoding? standardErrorEncoding = reader.ReadNullable(r => r.ReadEncoding());
|
||||
string? workingDirectory = reader.ReadNullableString();
|
||||
RootJailInfo? rootJailInfo = reader.ReadNullableStruct(r => BuildXL.Processes.RootJailInfo.Deserialize(r));
|
||||
IBuildParameters buildParameters = null;
|
||||
IBuildParameters? buildParameters = null;
|
||||
var envVars = reader.ReadNullable(r => r.ReadReadOnlyList(r2 => new KeyValuePair<string, string>(r2.ReadString(), r2.ReadString())));
|
||||
if (envVars != null)
|
||||
{
|
||||
buildParameters = BuildParameters.GetFactory().PopulateFromDictionary(envVars.ToDictionary(kvp => kvp.Key, kvp => kvp.Value));
|
||||
}
|
||||
|
||||
string[] allowedSurvivingChildNames = reader.ReadNullable(r => r.ReadReadOnlyList(r2 => r2.ReadString()))?.ToArray();
|
||||
string[]? allowedSurvivingChildNames = reader.ReadNullable(r => r.ReadReadOnlyList(r2 => r2.ReadString()))?.ToArray();
|
||||
int maxLengthInMemory = reader.ReadInt32();
|
||||
TimeSpan? timeout = reader.ReadNullableStruct(r => r.ReadTimeSpan());
|
||||
TimeSpan nestedProcessTerminationTimeout = reader.ReadTimeSpan();
|
||||
long pipSemiStableHash = reader.ReadInt64();
|
||||
string timeoutDumpDirectory = reader.ReadNullableString();
|
||||
string survivingPipProcessChildrenDumpDirectory = reader.ReadNullableString();
|
||||
string? timeoutDumpDirectory = reader.ReadNullableString();
|
||||
string? survivingPipProcessChildrenDumpDirectory = reader.ReadNullableString();
|
||||
SandboxKind sandboxKind = (SandboxKind)reader.ReadByte();
|
||||
string pipDescription = reader.ReadNullableString();
|
||||
string? pipDescription = reader.ReadNullableString();
|
||||
SandboxedProcessStandardFiles sandboxedProcessStandardFiles = SandboxedProcessStandardFiles.Deserialize(reader);
|
||||
StandardInputInfo standardInputSourceInfo = reader.ReadNullable(r => StandardInputInfo.Deserialize(r));
|
||||
SandboxObserverDescriptor standardObserverDescriptor = reader.ReadNullable(r => SandboxObserverDescriptor.Deserialize(r));
|
||||
StandardInputInfo? standardInputSourceInfo = reader.ReadNullable(r => StandardInputInfo.Deserialize(r));
|
||||
SandboxObserverDescriptor? standardObserverDescriptor = reader.ReadNullable(r => SandboxObserverDescriptor.Deserialize(r));
|
||||
int numRetriesPipeReadOnCancel = reader.ReadInt32();
|
||||
|
||||
(string source, string target)[] redirectedTempFolder = reader.ReadNullable(r => r.ReadReadOnlyList(r2 => (source: r2.ReadString(), target: r2.ReadString())))?.ToArray();
|
||||
(string source, string target)[]? redirectedTempFolder = reader.ReadNullable(r => r.ReadReadOnlyList(r2 => (source: r2.ReadString(), target: r2.ReadString())))?.ToArray();
|
||||
|
||||
var sidebandWritter = reader.ReadNullable(r => SidebandWriter.Deserialize(r));
|
||||
var createJobObjectForCurrentProcess = reader.ReadBoolean();
|
||||
|
|
|
@ -86,7 +86,7 @@ namespace BuildXL.Processes
|
|||
/// <summary>
|
||||
/// Allowed surviving child process names.
|
||||
/// </summary>
|
||||
private string[] AllowedSurvivingChildProcessNames { get; }
|
||||
private string[]? AllowedSurvivingChildProcessNames { get; }
|
||||
|
||||
private bool IgnoreReportedAccesses { get; }
|
||||
|
||||
|
@ -151,7 +151,7 @@ namespace BuildXL.Processes
|
|||
|
||||
PipId = info.FileAccessManifest.PipId;
|
||||
|
||||
SandboxConnection = info.SandboxConnection;
|
||||
SandboxConnection = info.SandboxConnection!;
|
||||
ChildProcessTimeout = info.NestedProcessTerminationTimeout;
|
||||
AllowedSurvivingChildProcessNames = info.AllowedSurvivingChildProcessNames;
|
||||
ReportQueueProcessTimeoutForTests = info.ReportQueueProcessTimeoutForTests;
|
||||
|
@ -268,9 +268,14 @@ namespace BuildXL.Processes
|
|||
// inside the jail, run "bxl-env" to change into user-specified directory as well as to set environment variables before running user-specified program
|
||||
// NOTE: -C <dir> must be the first two arguments, see bxl-env.c
|
||||
process.StartInfo.ArgumentList.Add(info.RootJailInfo.CopyToRootJailIfNeeded(EnvExecutable));
|
||||
// change directory into what the user specified
|
||||
process.StartInfo.ArgumentList.Add("-C");
|
||||
process.StartInfo.ArgumentList.Add(info.WorkingDirectory);
|
||||
|
||||
if (info.WorkingDirectory != null)
|
||||
{
|
||||
// change directory into what the user specified
|
||||
process.StartInfo.ArgumentList.Add("-C");
|
||||
process.StartInfo.ArgumentList.Add(info.WorkingDirectory);
|
||||
}
|
||||
|
||||
// propagate environment variables (because root jail program won't do it)
|
||||
process.StartInfo.ArgumentList.Add("-i");
|
||||
foreach (var kvp in process.StartInfo.Environment.Select(kvp => (kvp.Key, kvp.Value)).Concat(AdditionalEnvVars(info)))
|
||||
|
@ -292,7 +297,7 @@ namespace BuildXL.Processes
|
|||
// When executed using external tool, the manifest tree has been sealed, and cannot be modified.
|
||||
// We take care of adding this path in the manifest in SandboxedProcessPipExecutor.cs;
|
||||
// see AddUnixSpecificSandcboxedProcessFileAccessPolicies
|
||||
if (!info.FileAccessManifest.IsManifestTreeBlockSealed)
|
||||
if (!info.FileAccessManifest!.IsManifestTreeBlockSealed)
|
||||
{
|
||||
info.FileAccessManifest.AddPath(
|
||||
AbsolutePath.Create(PathTable, process.StartInfo.FileName),
|
||||
|
@ -355,7 +360,7 @@ namespace BuildXL.Processes
|
|||
// NOTE: just by not keeping any references to 'info' should make the FileAccessManifest object
|
||||
// unreachable and thus available for garbage collection. We call Release() here explicitly
|
||||
// just to emphasize the importance of reclaiming this memory.
|
||||
info.FileAccessManifest.Release();
|
||||
info.FileAccessManifest!.Release();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -517,7 +522,7 @@ namespace BuildXL.Processes
|
|||
m_pendingReports.Post(report);
|
||||
}
|
||||
|
||||
private static string? EnsureQuoted(string cmdLineArgs)
|
||||
private static string? EnsureQuoted(string? cmdLineArgs)
|
||||
{
|
||||
#if NETCOREAPP
|
||||
if (cmdLineArgs == null)
|
||||
|
@ -584,7 +589,7 @@ namespace BuildXL.Processes
|
|||
|
||||
lines.Add("set -e");
|
||||
|
||||
if (info.SandboxConnection.Kind == SandboxKind.MacOsHybrid || info.SandboxConnection.Kind == SandboxKind.MacOsDetours)
|
||||
if (info.SandboxConnection!.Kind == SandboxKind.MacOsHybrid || info.SandboxConnection.Kind == SandboxKind.MacOsDetours)
|
||||
{
|
||||
lines.Add($"export {DetoursEnvVar}={DetoursFile}");
|
||||
}
|
||||
|
@ -608,7 +613,7 @@ namespace BuildXL.Processes
|
|||
|
||||
private IEnumerable<(string, string?)> AdditionalEnvVars(SandboxedProcessInfo info)
|
||||
{
|
||||
return info.SandboxConnection
|
||||
return info.SandboxConnection!
|
||||
.AdditionalEnvVarsToSet(info, UniqueName)
|
||||
.Concat(info.SandboxConnection.Kind == SandboxKind.MacOsHybrid || info.SandboxConnection.Kind == SandboxKind.MacOsDetours
|
||||
? new (string, string?)[] { (DetoursEnvVar, DetoursFile) }
|
||||
|
@ -905,7 +910,7 @@ namespace BuildXL.Processes
|
|||
// When executed using external tool, the manifest tree has been sealed, and cannot be modified.
|
||||
// We take care of adding this path in the manifest in SandboxedProcessPipExecutor.cs;
|
||||
// see AddUnixSpecificSandcboxedProcessFileAccessPolicies
|
||||
if (!info.FileAccessManifest.IsManifestTreeBlockSealed)
|
||||
if (!info.FileAccessManifest!.IsManifestTreeBlockSealed)
|
||||
{
|
||||
info.FileAccessManifest.AddPath(
|
||||
AbsolutePath.Create(PathTable, stdinFileName),
|
||||
|
|
|
@ -143,9 +143,9 @@ namespace BuildXL.Processes
|
|||
PipDescription = info.PipDescription;
|
||||
PipSemiStableHash = info.PipSemiStableHash;
|
||||
TimeoutDumpDirectory = info.TimeoutDumpDirectory;
|
||||
ShouldReportFileAccesses = info.FileAccessManifest?.ReportFileAccesses == true;
|
||||
ShouldReportFileAccesses = info.FileAccessManifest.ReportFileAccesses;
|
||||
DetoursListener = info.DetoursEventListener;
|
||||
UniqueName = $"Pip{info.FileAccessManifest?.PipId:X}.{Interlocked.Increment(ref m_uniqueNameCounter)}";
|
||||
UniqueName = $"Pip{info.FileAccessManifest.PipId:X}.{Interlocked.Increment(ref m_uniqueNameCounter)}";
|
||||
|
||||
info.Timeout ??= s_defaultProcessTimeout;
|
||||
|
||||
|
|
|
@ -489,33 +489,35 @@ namespace BuildXL.SandboxedProcessExecutor
|
|||
}
|
||||
}
|
||||
|
||||
foreach (var tmpEnvVar in BuildParameters.DisallowedTempVariables)
|
||||
if (info.EnvironmentVariables != null)
|
||||
{
|
||||
if (info.EnvironmentVariables.ContainsKey(tmpEnvVar))
|
||||
foreach (var tmpEnvVar in BuildParameters.DisallowedTempVariables)
|
||||
{
|
||||
string tempPath = info.EnvironmentVariables[tmpEnvVar];
|
||||
var result = PrepareTempDirectory(tempPath);
|
||||
if (info.EnvironmentVariables.ContainsKey(tmpEnvVar))
|
||||
{
|
||||
string tempPath = info.EnvironmentVariables[tmpEnvVar];
|
||||
var result = PrepareTempDirectory(tempPath);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
m_logger.LogError($"Failed to prepare temporary directory '{tempPath}': {result.Failure.DescribeIncludingInnerFailures()}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string vmSharedTemp = info.EnvironmentVariables.TryGetValue(VmSpecialEnvironmentVariables.VmSharedTemp, null);
|
||||
if (!string.IsNullOrEmpty(vmSharedTemp))
|
||||
{
|
||||
// Ensure that the directory exists, but do not clean if it already exists because the directory is shared by multiple pips.
|
||||
var result = PrepareTempDirectory(vmSharedTemp, cleanDirectoryIfExists: false);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
m_logger.LogError($"Failed to prepare temporary directory '{tempPath}': {result.Failure.DescribeIncludingInnerFailures()}");
|
||||
m_logger.LogError($"Failed to prepare VM shared temporary directory '{vmSharedTemp}': {result.Failure.DescribeIncludingInnerFailures()}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string vmSharedTemp = info.EnvironmentVariables.TryGetValue(VmSpecialEnvironmentVariables.VmSharedTemp, null);
|
||||
|
||||
if (!string.IsNullOrEmpty(vmSharedTemp))
|
||||
{
|
||||
// Ensure that the directory exists, but do not clean if it already exists because the directory is shared by multiple pips.
|
||||
var result = PrepareTempDirectory(vmSharedTemp, cleanDirectoryIfExists: false);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
m_logger.LogError($"Failed to prepare VM shared temporary directory '{vmSharedTemp}': {result.Failure.DescribeIncludingInnerFailures()}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (remoteData == null)
|
||||
{
|
||||
return true;
|
||||
|
@ -610,7 +612,7 @@ namespace BuildXL.SandboxedProcessExecutor
|
|||
|
||||
result.LastMessageCount = process.GetLastMessageCount();
|
||||
result.DetoursMaxHeapSize = process.GetDetoursMaxHeapSize();
|
||||
result.MessageCountSemaphoreCreated = info.FileAccessManifest.MessageCountSemaphore != null;
|
||||
result.MessageCountSemaphoreCreated = fam.MessageCountSemaphore != null;
|
||||
|
||||
return (ExitCode.Success, result);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче