|
1 | 1 | using System; |
| 2 | +using System.Collections.Generic; |
2 | 3 | using System.Diagnostics; |
3 | 4 | using System.IO; |
| 5 | +using System.Linq; |
4 | 6 | using System.Runtime.InteropServices; |
| 7 | +using System.Threading; |
| 8 | +using System.Threading.Tasks; |
| 9 | +using Microsoft.Extensions.Logging; |
5 | 10 |
|
6 | 11 | namespace Npgsql; |
7 | 12 |
|
@@ -40,7 +45,7 @@ static string? SystemServiceFilePath |
40 | 45 |
|
41 | 46 | internal static string? SystemServiceFile |
42 | 47 | => SystemServiceFilePath is string path |
43 | | - ? Path.Combine(SystemServiceFilePath, "pg_service.conf") |
| 48 | + ? Path.Combine(path, "pg_service.conf") |
44 | 49 | : null; |
45 | 50 |
|
46 | 51 | internal static string? SslCert => Environment.GetEnvironmentVariable("PGSSLCERT"); |
@@ -82,27 +87,92 @@ internal static string? SslCertRootDefault |
82 | 87 |
|
83 | 88 | static string? GetSystemPostgresDir() => Environment.GetEnvironmentVariable("PGSYSCONFDIR"); |
84 | 89 |
|
85 | | - static string? GetDefaultSystemPostgresDir() => ExecuteCommandSync("pg_config", "--sysconfdir"); |
86 | | - |
87 | | - static string? ExecuteCommandSync(string command, string arguments) |
| 90 | + static string? GetDefaultSystemPostgresDir() |
88 | 91 | { |
89 | 92 | try |
90 | 93 | { |
91 | | - using var process = new Process(); |
92 | | - process.StartInfo.FileName = command; |
93 | | - process.StartInfo.Arguments = arguments; |
94 | | - process.StartInfo.RedirectStandardOutput = true; |
95 | | - process.StartInfo.UseShellExecute = false; |
96 | | - process.Start(); |
97 | | - |
98 | | - var output = process.StandardOutput.ReadToEnd().Trim(); |
99 | | - process.WaitForExit(); |
100 | | - |
101 | | - return process.ExitCode == 0 ? output : null; |
| 94 | + return ExecuteCommandAsync("pg_config", async: false, "--sysconfdir").GetAwaiter().GetResult()?.FirstOrDefault(); |
102 | 95 | } |
103 | | - catch (Exception) |
| 96 | + catch |
104 | 97 | { |
105 | 98 | return null; |
106 | 99 | } |
107 | 100 | } |
| 101 | + |
| 102 | + internal static ValueTask<string[]?> ExecuteCommandAsync( |
| 103 | + string processName, |
| 104 | + bool async, |
| 105 | + string arguments = "", |
| 106 | + ILogger? logger = default, |
| 107 | + CancellationToken cancellationToken = default) |
| 108 | + { |
| 109 | + var processPath = FindInPath(processName); |
| 110 | + if (processPath == null) |
| 111 | + { |
| 112 | + logger?.LogDebug($"{processName} not found in PATH"); |
| 113 | + return new((string[]?)null); |
| 114 | + } |
| 115 | + var processStartInfo = new ProcessStartInfo |
| 116 | + { |
| 117 | + FileName = processPath, |
| 118 | + Arguments = arguments, |
| 119 | + RedirectStandardOutput = true, |
| 120 | + RedirectStandardError = true, |
| 121 | + UseShellExecute = false |
| 122 | + }; |
| 123 | + |
| 124 | + var process = Process.Start(processStartInfo); |
| 125 | + if (process is null) |
| 126 | + { |
| 127 | + logger?.LogDebug($"{processName} process could not be started"); |
| 128 | + return new((string[]?)null); |
| 129 | + } |
| 130 | + |
| 131 | + return ExecuteCommandAsyncInternal(); |
| 132 | + |
| 133 | +#pragma warning disable CS1998 |
| 134 | + async ValueTask<string[]?> ExecuteCommandAsyncInternal() |
| 135 | +#pragma warning restore CS1998 |
| 136 | + { |
| 137 | +#if NET5_0_OR_GREATER |
| 138 | + if (async) |
| 139 | + await process.WaitForExitAsync(cancellationToken); |
| 140 | + else |
| 141 | + // ReSharper disable once MethodHasAsyncOverloadWithCancellation |
| 142 | + process.WaitForExit(); |
| 143 | +#else |
| 144 | + // ReSharper disable once MethodHasAsyncOverload |
| 145 | + process.WaitForExit(); |
| 146 | +#endif |
| 147 | + |
| 148 | + if (process.ExitCode != 0) |
| 149 | + { |
| 150 | + logger?.LogDebug($"{processName} exited with code {process.ExitCode}: {process.StandardError.ReadToEnd()}"); |
| 151 | + return null; |
| 152 | + } |
| 153 | + |
| 154 | + var lines = new List<string>(); |
| 155 | + string? line; |
| 156 | + // ReSharper disable once MethodHasAsyncOverload |
| 157 | +#if NET7_0_OR_GREATER |
| 158 | + while ((line = async ? await process.StandardOutput.ReadLineAsync(cancellationToken) : process.StandardOutput.ReadLine()) is not null) |
| 159 | +#elif NET5_0_OR_GREATER |
| 160 | + while ((line = async ? await process.StandardOutput.ReadLineAsync() : process.StandardOutput.ReadLine()) is not null) |
| 161 | +#else |
| 162 | + while ((line = process.StandardOutput.ReadLine()) is not null) |
| 163 | +#endif |
| 164 | + { |
| 165 | + lines.Add(line); |
| 166 | + } |
| 167 | + |
| 168 | + return lines.ToArray(); |
| 169 | + } |
| 170 | + } |
| 171 | + |
| 172 | + static string? FindInPath(string name) => Environment.GetEnvironmentVariable("PATH") |
| 173 | + ?.Split(Path.PathSeparator) |
| 174 | + .Select(p => Path.Combine(p, name + ExecutableExtension)) |
| 175 | + .FirstOrDefault(File.Exists); |
| 176 | + |
| 177 | + static string ExecutableExtension => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : ""; |
108 | 178 | } |
0 commit comments