using System.Diagnostics; using System.Runtime.InteropServices; using System.Text.Json; namespace Quorate.HostExec; internal static class Proxy { private static readonly JsonSerializerOptions Json = new() { PropertyNameCaseInsensitive = true }; public static async Task Run(string wrapper, IReadOnlyList arguments) { var fullWrapper = Path.GetFullPath(wrapper); var root = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(fullWrapper)!, "../../..")); var config = Read($"{fullWrapper}.json"); if (config.Version != 1 || config.Kind != "gnu") { throw new InvalidDataException("unsupported proxy configuration"); } var toolRoot = ToolRoot.Open(root); var executable = toolRoot.Resolve(config.File); if (!File.Exists(executable)) { throw new FileNotFoundException("proxied tool is unavailable", executable); } var start = new ProcessStartInfo(executable) { UseShellExecute = false }; foreach (var argument in WithSysroot(root, arguments)) { start.ArgumentList.Add(argument); } using var stop = new CancellationTokenSource(); var signalCode = 143; using var interrupt = PosixSignalRegistration.Create(PosixSignal.SIGINT, context => { context.Cancel = true; signalCode = 130; stop.Cancel(); }); using var terminate = PosixSignalRegistration.Create(PosixSignal.SIGTERM, context => { context.Cancel = true; signalCode = 143; stop.Cancel(); }); using var child = Process.Start(start) ?? throw new InvalidOperationException("proxied tool did not start"); try { await child.WaitForExitAsync(stop.Token).ConfigureAwait(false); return child.ExitCode; } catch (OperationCanceledException) when (stop.IsCancellationRequested) { if (!child.HasExited) { child.Kill(true); await child.WaitForExitAsync().ConfigureAwait(false); } return signalCode; } } internal static IReadOnlyList WithSysroot(string root, IReadOnlyList arguments) { var fullRoot = Path.GetFullPath(root); var found = false; for (var index = 0; index < arguments.Count; index++) { var argument = arguments[index]; string? value = null; if (argument == "--sysroot") { if (++index >= arguments.Count) { throw new ArgumentException("sysroot value is missing", nameof(arguments)); } value = arguments[index]; } else if (argument.StartsWith("--sysroot=", StringComparison.Ordinal)) { value = argument[10..]; } if (value is not null) { if (!Path.GetFullPath(value).Equals(fullRoot, StringComparison.Ordinal)) { throw new ArgumentException("conflicting sysroot", nameof(arguments)); } found = true; } } return found ? arguments : [$"--sysroot={fullRoot}", .. arguments]; } private static ProxyFile Read(string path) { var value = JsonSerializer.Deserialize(File.ReadAllBytes(path), Json); return value ?? throw new InvalidDataException("proxy configuration is empty"); } }