-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Expand file tree
/
Copy pathWslUtils.cs
More file actions
301 lines (257 loc) · 11.5 KB
/
WslUtils.cs
File metadata and controls
301 lines (257 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace GitCredentialManager
{
public enum WindowsShell
{
Cmd,
PowerShell,
}
public static class WslUtils
{
private const string WslUncPrefix = @"\\wsl$\";
private const string WslLocalHostUncPrefix = @"\\wsl.localhost\";
private const string WslCommandName = "wsl.exe";
private const string WslConfFilePath = "/etc/wsl.conf";
private const string DefaultWslMountPrefix = "/mnt";
private const string DefaultWslSysDriveMountName = "c";
internal const string WslViewShellHandlerName = "wslview";
/// <summary>
/// Cached Windows host session ID.
/// </summary>
/// <remarks>A value less than 0 represents "unknown".</remarks>
private static int _windowsSessionId = -1;
/// <summary>
/// Cached WSL version.
/// </summary>
/// <remarks>A value of 0 represents "not WSL", and a value less than 0 represents "unknown".</remarks>
private static int _wslVersion = -1;
/// <summary>
/// Cached Windows system drive mount path.
/// </summary>
private static string _sysDriveMountPath = null;
public static bool IsWslDistribution(IEnvironment env, IFileSystem fs, out int wslVersion)
{
if (_wslVersion < 0)
{
_wslVersion = GetWslVersion(env, fs);
}
wslVersion = _wslVersion;
return _wslVersion > 0;
}
private static int GetWslVersion(IEnvironment env, IFileSystem fs)
{
// All WSL distributions are Linux.. obviously!
if (!PlatformUtils.IsLinux())
{
return 0;
}
const string procWslInteropPath = "/proc/sys/fs/binfmt_misc/WSLInterop";
const string procWslInteropLatePath = "/proc/sys/fs/binfmt_misc/WSLInterop-Late";
// The WSLInterop file exists in WSL1 distributions
if (fs.FileExists(procWslInteropPath))
{
return 1;
}
// The WSLInterop-Late file exists in WSL2 distributions
if (fs.FileExists(procWslInteropLatePath))
{
return 2;
}
return 0;
}
/// <summary>
/// Test if a file path points to a location in a Windows Subsystem for Linux distribution.
/// </summary>
/// <param name="path">Path to test.</param>
/// <returns>True if <paramref name="path"/> is a WSL path, false otherwise.</returns>
public static bool IsWslPath(string path)
{
if (string.IsNullOrWhiteSpace(path)) return false;
return (path.StartsWith(WslUncPrefix, StringComparison.OrdinalIgnoreCase) &&
path.Length > WslUncPrefix.Length) ||
(path.StartsWith(WslLocalHostUncPrefix, StringComparison.OrdinalIgnoreCase) &&
path.Length > WslLocalHostUncPrefix.Length);
}
/// <summary>
/// Create a command to be executed in a Windows Subsystem for Linux distribution.
/// </summary>
/// <param name="distribution">WSL distribution name.</param>
/// <param name="command">Command to execute.</param>
/// <param name="trace2">The applications TRACE2 tracer.</param>
/// <param name="workingDirectory">Optional working directory.</param>
/// <returns><see cref="Process"/> object ready to start.</returns>
public static ChildProcess CreateWslProcess(string distribution,
string command,
ITrace2 trace2,
string workingDirectory = null)
{
var args = new StringBuilder();
args.AppendFormat("--distribution {0} ", distribution);
args.AppendFormat("--exec {0}", command);
string wslExePath = GetWslPath();
var psi = new ProcessStartInfo(wslExePath, args.ToString())
{
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = false, // Do not redirect stderr as tracing might be enabled
UseShellExecute = false,
WorkingDirectory = workingDirectory ?? string.Empty
};
return new ChildProcess(trace2, psi);
}
/// <summary>
/// Create a command to be executed in a shell in the host Windows operating system.
/// </summary>
/// <param name="fs">File system.</param>
/// <param name="shell">Shell used to execute the command in Windows.</param>
/// <param name="command">Command to execute.</param>
/// <param name="workingDirectory">Optional working directory.</param>
/// <returns><see cref="Process"/> object ready to start.</returns>
public static Process CreateWindowsShellProcess(IFileSystem fs,
WindowsShell shell, string command, string workingDirectory = null)
{
string sysDrive = GetSystemDriveMountPath(fs);
string launcher;
var args = new StringBuilder();
switch (shell)
{
case WindowsShell.Cmd:
launcher = Path.Combine(sysDrive, "Windows/cmd.exe");
args.AppendFormat("/C {0}", command);
break;
case WindowsShell.PowerShell:
const string psStreamSetup =
"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; " +
"[Console]::InputEncoding = [System.Text.Encoding]::UTF8; ";
launcher = Path.Combine(sysDrive, "Windows/System32/WindowsPowerShell/v1.0/powershell.exe");
args.Append(" -NoProfile -NonInteractive -ExecutionPolicy Bypass");
args.AppendFormat(" -Command \"{0} {1}\"", psStreamSetup, command);
break;
default:
throw new ArgumentOutOfRangeException(nameof(shell));
}
var psi = new ProcessStartInfo(launcher, args.ToString())
{
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
WorkingDirectory = workingDirectory ?? string.Empty
};
return new Process { StartInfo = psi };
}
/// <summary>
/// Get the host Windows session ID.
/// </summary>
/// <returns>Windows session ID, or a negative value if it is not known.</returns>
public static int GetWindowsSessionId(IFileSystem fs)
{
if (_windowsSessionId < 0)
{
const string script = @"(Get-Process -ID $PID).SessionId";
using (Process proc = CreateWindowsShellProcess(fs, WindowsShell.PowerShell, script))
{
proc.Start();
proc.WaitForExit();
if (proc.ExitCode == 0)
{
string output = proc.StandardOutput.ReadToEnd().Trim();
if (int.TryParse(output, out int sessionId))
{
_windowsSessionId = sessionId;
}
}
}
}
return _windowsSessionId;
}
private static string GetSystemDriveMountPath(IFileSystem fs)
{
if (_sysDriveMountPath is null)
{
string mountPrefix = DefaultWslMountPrefix;
// If the wsl.conf file exists in this distribution the user may
// have changed the Windows volume mount point prefix. Use it!
if (fs.FileExists(WslConfFilePath))
{
// Read wsl.conf for [automount] root = <path>
IniFile wslConf = IniSerializer.Deserialize(fs, WslConfFilePath);
if (wslConf.TryGetSection("automount", out IniSection automountSection) &&
automountSection.TryGetProperty("root", out string value))
{
mountPrefix = value;
}
}
// Try to locate the system volume by looking for the Windows\System32 directory
IEnumerable<string> mountPoints = fs.EnumerateDirectories(mountPrefix);
foreach (string mountPoint in mountPoints)
{
string sys32Path = Path.Combine(mountPoint, "Windows", "System32");
if (fs.DirectoryExists(sys32Path))
{
_sysDriveMountPath = mountPoint;
return _sysDriveMountPath;
}
}
_sysDriveMountPath = Path.Combine(mountPrefix, DefaultWslSysDriveMountName);
}
return _sysDriveMountPath;
}
public static string ConvertToDistroPath(string path, out string distribution)
{
if (!IsWslPath(path)) throw new ArgumentException("Must provide a WSL path", nameof(path));
int distroStart;
if (path.StartsWith(WslUncPrefix, StringComparison.OrdinalIgnoreCase))
{
distroStart = WslUncPrefix.Length;
}
else if (path.StartsWith(WslLocalHostUncPrefix, StringComparison.OrdinalIgnoreCase))
{
distroStart = WslLocalHostUncPrefix.Length;
}
else
{
throw new Exception("Invalid WSL path prefix");
}
int distroEnd = path.IndexOf('\\', distroStart);
if (distroEnd < 0) distroEnd = path.Length;
distribution = path.Substring(distroStart, distroEnd - distroStart);
if (path.Length > distroEnd)
{
return path.Substring(distroEnd).Replace('\\', '/');
}
return "/";
}
internal /*for testing purposes*/ static string GetWslPath()
{
// WSL is only supported on 64-bit operating systems
if (!Environment.Is64BitOperatingSystem)
{
throw new Exception("WSL is not supported on 32-bit operating systems");
}
//
// When running as a 32-bit application on a 64-bit operating system, we cannot access the real
// C:\Windows\System32 directory because the OS will redirect us transparently to the
// C:\Windows\SysWOW64 directory (containing 32-bit executables).
//
// In order to access the real 64-bit System32 directory, we must access via the pseudo directory
// C:\Windows\SysNative that does **not** experience any redirection for 32-bit applications.
//
// HOWEVER, if we're running as a 64-bit application on a 64-bit operating system, the SysNative
// directory does not exist! This means if running as a 64-bit application on a 64-bit OS we must
// use the System32 directory name directly.
//
var sysDir = Environment.ExpandEnvironmentVariables(
Environment.Is64BitProcess
? @"%WINDIR%\System32"
: @"%WINDIR%\SysNative"
);
return Path.Combine(sysDir, WslCommandName);
}
}
}