Skip to content

Commit 5492bb9

Browse files
committed
Create ExecuteCommandAsync static method
1 parent 95dbc3b commit 5492bb9

2 files changed

Lines changed: 96 additions & 84 deletions

File tree

Lines changed: 10 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
using System;
2-
using System.Diagnostics;
3-
using System.IO;
4-
using System.Linq;
52
using System.Threading;
63
using System.Threading.Tasks;
74
using Microsoft.Extensions.Logging;
@@ -18,75 +15,25 @@ sealed class KerberosUsernameProvider
1815
static string? _principalWithRealm;
1916
static string? _principalWithoutRealm;
2017

21-
internal static ValueTask<string?> GetUsernameAsync(bool includeRealm, ILogger connectionLogger, bool async, CancellationToken cancellationToken)
18+
internal static async ValueTask<string?> GetUsernameAsync(bool includeRealm, ILogger connectionLogger, bool async, CancellationToken cancellationToken)
2219
{
2320
if (_performedDetection)
24-
return new(includeRealm ? _principalWithRealm : _principalWithoutRealm);
25-
var klistPath = FindInPath("klist");
26-
if (klistPath == null)
27-
{
28-
connectionLogger.LogDebug("klist not found in PATH, skipping Kerberos username detection");
29-
return new((string?)null);
30-
}
31-
var processStartInfo = new ProcessStartInfo
32-
{
33-
FileName = klistPath,
34-
RedirectStandardOutput = true,
35-
RedirectStandardError = true,
36-
UseShellExecute = false
37-
};
21+
return includeRealm ? _principalWithRealm : _principalWithoutRealm;
3822

39-
var process = Process.Start(processStartInfo);
40-
if (process is null)
23+
var lines = await PostgresEnvironment.ExecuteCommandAsync("klist", async, logger: connectionLogger, cancellationToken: cancellationToken);
24+
if (lines is null)
4125
{
42-
connectionLogger.LogDebug("klist process could not be started");
43-
return new((string?)null);
26+
connectionLogger.LogDebug("Skipping Kerberos username detection");
27+
return null;
4428
}
4529

46-
return GetUsernameAsyncInternal();
47-
48-
#pragma warning disable CS1998
49-
async ValueTask<string?> GetUsernameAsyncInternal()
50-
#pragma warning restore CS1998
51-
{
52-
#if NET5_0_OR_GREATER
53-
if (async)
54-
await process.WaitForExitAsync(cancellationToken);
55-
else
56-
// ReSharper disable once MethodHasAsyncOverloadWithCancellation
57-
process.WaitForExit();
58-
#else
59-
// ReSharper disable once MethodHasAsyncOverload
60-
process.WaitForExit();
61-
#endif
62-
63-
if (process.ExitCode != 0)
64-
{
65-
connectionLogger.LogDebug($"klist exited with code {process.ExitCode}: {process.StandardError.ReadToEnd()}");
66-
return null;
67-
}
68-
69-
var line = default(string);
70-
for (var i = 0; i < 2; i++)
71-
// ReSharper disable once MethodHasAsyncOverload
72-
#if NET7_0_OR_GREATER
73-
if ((line = async ? await process.StandardOutput.ReadLineAsync(cancellationToken) : process.StandardOutput.ReadLine()) == null)
74-
#elif NET5_0_OR_GREATER
75-
if ((line = async ? await process.StandardOutput.ReadLineAsync() : process.StandardOutput.ReadLine()) == null)
76-
#else
77-
if ((line = process.StandardOutput.ReadLine()) == null)
78-
#endif
79-
{
80-
connectionLogger.LogDebug("Unexpected output from klist, aborting Kerberos username detection");
81-
return null;
82-
}
83-
84-
return ParseKListOutput(line!, includeRealm, connectionLogger);
85-
}
30+
return ParseKListOutput(lines, includeRealm, connectionLogger);
8631
}
8732

88-
static string? ParseKListOutput(string line, bool includeRealm, ILogger connectionLogger)
33+
static string? ParseKListOutput(string[] lines, bool includeRealm, ILogger connectionLogger)
8934
{
35+
if (lines.Length < 2) return null;
36+
var line = lines[1];
9037
var colonIndex = line.IndexOf(':');
9138
var colonLastIndex = line.LastIndexOf(':');
9239
if (colonIndex == -1 || colonIndex != colonLastIndex)
@@ -111,9 +58,4 @@ sealed class KerberosUsernameProvider
11158
_performedDetection = true;
11259
return includeRealm ? _principalWithRealm : _principalWithoutRealm;
11360
}
114-
115-
static string? FindInPath(string name) => Environment.GetEnvironmentVariable("PATH")
116-
?.Split(Path.PathSeparator)
117-
.Select(p => Path.Combine(p, name))
118-
.FirstOrDefault(File.Exists);
11961
}

src/Npgsql/PostgresEnvironment.cs

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics;
34
using System.IO;
5+
using System.Linq;
46
using System.Runtime.InteropServices;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.Extensions.Logging;
510

611
namespace Npgsql;
712

@@ -40,7 +45,7 @@ static string? SystemServiceFilePath
4045

4146
internal static string? SystemServiceFile
4247
=> SystemServiceFilePath is string path
43-
? Path.Combine(SystemServiceFilePath, "pg_service.conf")
48+
? Path.Combine(path, "pg_service.conf")
4449
: null;
4550

4651
internal static string? SslCert => Environment.GetEnvironmentVariable("PGSSLCERT");
@@ -82,27 +87,92 @@ internal static string? SslCertRootDefault
8287

8388
static string? GetSystemPostgresDir() => Environment.GetEnvironmentVariable("PGSYSCONFDIR");
8489

85-
static string? GetDefaultSystemPostgresDir() => ExecuteCommandSync("pg_config", "--sysconfdir");
86-
87-
static string? ExecuteCommandSync(string command, string arguments)
90+
static string? GetDefaultSystemPostgresDir()
8891
{
8992
try
9093
{
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();
10295
}
103-
catch (Exception)
96+
catch
10497
{
10598
return null;
10699
}
107100
}
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" : "";
108178
}

0 commit comments

Comments
 (0)