// Copyright Epic Games, Inc. All Rights Reserved. using EpicGames.Core; using Microsoft.Extensions.Logging; using System; using System.Buffers.Binary; using System.Buffers.Text; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; namespace EpicGames.Perforce { /// /// Wraps a call to a p4.exe child process, and allows reading data from it /// class PerforceChildProcess : IPerforceOutput, IDisposable { /// /// The process group /// ManagedProcessGroup ChildProcessGroup; /// /// The child process instance /// ManagedProcess ChildProcess; /// /// Scope object for tracing /// ITraceSpan Scope; /// /// The buffer data /// byte[] Buffer; /// /// End of the valid portion of the buffer (exclusive) /// int BufferEnd; /// public ReadOnlyMemory Data => Buffer.AsMemory(0, BufferEnd); /// /// Temp file containing file arguments /// string? TempFileName; /// /// Constructor /// /// /// Command line arguments /// File arguments, which may be placed in a response file /// Input data to pass to the child process /// /// Logging device public PerforceChildProcess(string Command, IReadOnlyList Arguments, IReadOnlyList? FileArguments, byte[]? InputData, IReadOnlyList GlobalOptions, ILogger Logger) { string PerforceFileName = GetExecutable(); List FullArguments = new List(); FullArguments.Add("-G"); FullArguments.AddRange(GlobalOptions); if (FileArguments != null) { TempFileName = Path.GetTempFileName(); File.WriteAllLines(TempFileName, FileArguments); FullArguments.Add($"-x{TempFileName}"); } FullArguments.Add(Command); FullArguments.AddRange(Arguments); string FullArgumentList = CommandLineArguments.Join(FullArguments); Logger.LogDebug("Running {Executable} {Arguments}", PerforceFileName, FullArgumentList); Scope = TraceSpan.Create(Command, Service: "perforce"); Scope.AddMetadata("arguments", FullArgumentList); ChildProcessGroup = new ManagedProcessGroup(); ChildProcess = new ManagedProcess(ChildProcessGroup, PerforceFileName, FullArgumentList, null, null, InputData, ProcessPriorityClass.Normal); Buffer = new byte[64 * 1024]; } /// /// Gets the path to the P4.EXE executable /// /// Path to the executable public static string GetExecutable() { string PerforceFileName; if (RuntimePlatform.IsWindows) { PerforceFileName = "p4.exe"; } else { PerforceFileName = File.Exists("/usr/local/bin/p4") ? "/usr/local/bin/p4" : "/usr/bin/p4"; } return PerforceFileName; } /// public ValueTask DisposeAsync() { Dispose(); return new ValueTask(Task.CompletedTask); } /// public void Dispose() { if (ChildProcess != null) { ChildProcess.Dispose(); ChildProcess = null!; } if (ChildProcessGroup != null) { ChildProcessGroup.Dispose(); ChildProcessGroup = null!; } if (Scope != null) { Scope.Dispose(); Scope = null!; } if (TempFileName != null) { try { File.Delete(TempFileName); } catch { } TempFileName = null; } } /// public async Task ReadAsync(CancellationToken CancellationToken) { // Update the buffer contents if (BufferEnd == Buffer.Length) { Array.Resize(ref Buffer, Math.Min(Buffer.Length + (32 * 1024 * 1024), Buffer.Length * 2)); } // Try to read more data int PrevBufferEnd = BufferEnd; while (BufferEnd < Buffer.Length) { int Count = await ChildProcess!.ReadAsync(Buffer, BufferEnd, Buffer.Length - BufferEnd, CancellationToken); if (Count == 0) { break; } BufferEnd += Count; } return BufferEnd > PrevBufferEnd; } /// public void Discard(int NumBytes) { if (NumBytes > 0) { Array.Copy(Buffer, NumBytes, Buffer, 0, BufferEnd - NumBytes); BufferEnd -= NumBytes; } } /// /// Reads all output from the child process as a string /// /// Cancellation token to abort the read /// Exit code and output from the process public async Task> TryReadToEndAsync(CancellationToken CancellationToken) { using MemoryStream Stream = new MemoryStream(); while (await ReadAsync(CancellationToken)) { ReadOnlyMemory DataCopy = Data; Stream.Write(DataCopy.Span); Discard(DataCopy.Length); } string String = Encoding.Default.GetString(Stream.ToArray()); return Tuple.Create(ChildProcess.ExitCode == 0, String); } } }