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
#![allow(non_snake_case)]
use crate::{Error, Result};
use crate::{Configuration, Process, Stdio};
use crate::WSL_DISTRIBUTION_FLAGS;
use winapi::shared::minwindef::{BOOL, DWORD};
use winapi::shared::ntdef::{HANDLE, PCWSTR, PSTR, ULONG};
use winapi::shared::winerror::{SUCCEEDED, HRESULT, E_INVALIDARG};
use std::convert::TryInto;
use std::ffi::OsStr;
use std::fmt::Display;
use std::io;
use std::os::windows::ffi::OsStrExt;
use std::path::Path;
use std::ptr::null_mut;
// https://github.com/microsoft/WSL/issues/4645 - Wsl* may throw on null pointers, so avoid passing null pointers
/// A loaded `wslapi.dll` or `api-ms-win-wsl-api-l1-1-0.dll` instance
pub struct Library {
WslIsDistributionRegistered: unsafe fn (distributionName: PCWSTR) -> BOOL,
WslRegisterDistribution: unsafe fn (distributionName: PCWSTR, tarGzFilename: PCWSTR) -> HRESULT,
WslUnregisterDistribution: unsafe fn (distributionName: PCWSTR) -> HRESULT,
WslConfigureDistribution: unsafe fn (distributionName: PCWSTR, defaultUID: ULONG, wslDistributionFlags: WSL_DISTRIBUTION_FLAGS) -> HRESULT,
WslGetDistributionConfiguration: unsafe fn (distributionName: PCWSTR, distributionVersion: *mut ULONG, defaultUID: *mut ULONG, wslDistributionFlags: *mut WSL_DISTRIBUTION_FLAGS, defaultEnvironmentVariables: *mut *mut PSTR, defaultEnvironmentVariableCount: *mut ULONG) -> HRESULT,
WslLaunchInteractive: unsafe fn (distributionName: PCWSTR, command: PCWSTR, useCurrentWorkingDirectory: BOOL, exitCode: *mut DWORD) -> HRESULT,
WslLaunch: unsafe fn (distributionName: PCWSTR, command: PCWSTR, useCurrentWorkingDirectory: BOOL, stdIn: HANDLE, stdOut: HANDLE, stdErr: HANDLE, process: *mut HANDLE) -> HRESULT,
}
impl Library {
/// Attempt to load `wslapi.dll`
pub fn new() -> io::Result<Self> {
// fallback on api-ms-win-wsl-api-l1-1-0.dll etc.?
let lib = minidl::Library::load("wslapi.dll")?;
unsafe{Ok(Self{
WslIsDistributionRegistered: lib.sym("WslIsDistributionRegistered\0")?,
WslRegisterDistribution: lib.sym("WslRegisterDistribution\0")?,
WslUnregisterDistribution: lib.sym("WslUnregisterDistribution\0")?,
WslConfigureDistribution: lib.sym("WslConfigureDistribution\0")?,
WslGetDistributionConfiguration: lib.sym("WslGetDistributionConfiguration\0")?,
WslLaunchInteractive: lib.sym("WslLaunchInteractive\0")?,
WslLaunch: lib.sym("WslLaunch\0")?,
})}
}
/// Determines if a distribution is registered with the Windows Subsystem for Linux (WSL).
///
/// ### Arguments
///
/// * `distribution_name` - Unique name representing a distribution (for example, "Fabrikam.Distro.10.01").
///
/// ### Returns
///
/// - `true` if the supplied distribution is currently registered
/// - `false` otherwise.
///
/// ### See Also
///
/// - [WslIsDistributionRegistered] - the underlying API
///
/// [WslIsDistributionRegistered]: https://docs.microsoft.com/en-us/windows/win32/api/wslapi/nf-wslapi-wslisdistributionregistered
pub fn is_distribution_registered(&self, distribution_name: impl AsRef<OsStr>) -> bool {
let distribution_name = distribution_name.as_ref().encode_wide().chain(Some(0)).collect::<Vec<_>>();
!distribution_name[..distribution_name.len()-1].contains(&0) && unsafe { (self.WslIsDistributionRegistered)(distribution_name.as_ptr()) } != 0
}
/// Registers a new distribution with the Windows Subsystem for Linux (WSL).
///
/// <span style="color: red">**Consider using `wsl --import <Distro> <InstalLocation> <FileName>` instead:**</span><br>
/// The directory containing the executable will be registered as the `BasePath` for `rootfs` / `temp` to be placed in.<br>
/// This odd design choice stems from [WslRegisterDistribution] itself! Wasn't that a bad choice as far back as Windows XP?<br>
/// This also limits you to a single registration per executable!
///
/// ### Arguments
///
/// * `distribution_name` - Unique name representing a distribution (for example, "Fabrikam.Distro.10.01").
/// * `tar_gz_filename` - Full path to a .tar.gz file containing the file system of the distribution to register.
///
/// ### Returns
///
/// - `Err(Error)` - if `distribution_name` contained `'\0'` characters
/// - `Err(Error)` - if `distribution_name` already existed
/// - `Err(Error)` - if `tar_gz_filename` contained `'\0'` characters
/// - `Err(Error)` - if `tar_gz_filename` wasn't an absolute path?
/// - `Err(Error)` - if `tar_gz_filename` wasn't a valid path
/// - `Err(Error)` - if the executable's directory already contains a registered distribution
/// - `Err(Error)` - if the executable's directory wasn't writable?
/// - `Err(Error)` - if [WslRegisterDistribution] otherwise failed
/// - `Ok(())` - otherwise
///
/// ### See Also
///
/// - [WslRegisterDistribution] - the underlying API
///
/// [WslRegisterDistribution]: https://docs.microsoft.com/en-us/windows/win32/api/wslapi/nf-wslapi-wslregisterdistribution
pub fn register_distribution(&self, distribution_name: impl AsRef<OsStr>, tar_gz_filename: impl AsRef<Path>) -> Result<()> {
let wname = distribution_name.as_ref().encode_wide().chain(Some(0)).collect::<Vec<_>>();
let wpath = tar_gz_filename.as_ref().as_os_str().encode_wide().chain(Some(0)).collect::<Vec<_>>();
if wname[..wname.len()-1].contains(&0) { return Err(Error { hresult: E_INVALIDARG, message: format!("register_distribution({:?}, {:?}) failed: distribution_name contained '\0'", distribution_name.as_ref(), tar_gz_filename.as_ref()) }); }
if wpath[..wpath.len()-1].contains(&0) { return Err(Error { hresult: E_INVALIDARG, message: format!("register_distribution({:?}, {:?}) failed: tar_gz_filename contained '\0'", distribution_name.as_ref(), tar_gz_filename.as_ref()) }); }
let hr = unsafe { (self.WslRegisterDistribution)(wname.as_ptr(), wpath.as_ptr()) };
if !SUCCEEDED(hr) { return Err(Error { hresult: hr, message: format!("WslRegisterDistribution({:?}, {:?}) failed with HRESULT 0x{:08x}", distribution_name.as_ref(), tar_gz_filename.as_ref(), hr) }); }
Ok(())
}
/// Unregisters a distribution from the Windows Subsystem for Linux (WSL).
///
/// ### Arguments
///
/// * `distribution_name` - Unique name representing a distribution (for example, "Fabrikam.Distro.10.01").
///
/// ### Returns
///
/// - `Err(Error)` - if `distribution_name` contained `'\0'` characters
/// - `Err(Error)` - if `distribution_name` didn't exist?
/// - `Err(Error)` - if [WslUnregisterDistribution] failed
/// - `Ok(())` - otherwise
///
/// ### See Also
///
/// - [WslUnregisterDistribution] - the underlying API
///
/// [WslUnregisterDistribution]: https://docs.microsoft.com/en-us/windows/win32/api/wslapi/nf-wslapi-wslunregisterdistribution
pub fn unregister_distribution(&self, distribution_name: impl AsRef<OsStr>) -> Result<()> {
let wname = distribution_name.as_ref().encode_wide().chain(Some(0)).collect::<Vec<_>>();
if wname[..wname.len()-1].contains(&0) { return Err(Error { hresult: E_INVALIDARG, message: format!("unregister_distribution({:?}) failed: distribution_name contained '\0'", distribution_name.as_ref()) }); }
let hr = unsafe { (self.WslUnregisterDistribution)(wname.as_ptr()) };
if !SUCCEEDED(hr) { return Err(Error { hresult: hr, message: format!("WslUnregisterDistribution({:?}) failed with HRESULT 0x{:08x}", distribution_name.as_ref(), hr) }); }
Ok(())
}
/// Modifies the behavior of a distribution registered with the Windows Subsystem for Linux (WSL).
///
/// ### Arguments
///
/// * `distribution_name` - Unique name representing a distribution (for example, "Fabrikam.Distro.10.01").
/// * `default_uid` - The Linux user ID to use when launching new WSL sessions for this distribution.
/// * `wsl_distribution_flags` - Flags specifying what behavior to use for this distribution.
///
/// ### Returns
///
/// - `Err(Error)` - if `distribution_name` contained `'\0'` characters
/// - `Err(Error)` - if `distribution_name` didn't exist?
/// - `Err(Error)` - if [WslConfigureDistribution] otherwise failed (invalid uid? invalid flags?)
/// - `Ok(())` - otherwise
///
/// ### Returns
///
/// [WslConfigureDistribution]: https://docs.microsoft.com/en-us/windows/win32/api/wslapi/nf-wslapi-wslconfiguredistribution
pub fn configure_distribution(&self, distribution_name: impl AsRef<OsStr>, default_uid: ULONG, wsl_distribution_flags: WSL_DISTRIBUTION_FLAGS) -> Result<()> {
let wname = distribution_name.as_ref().encode_wide().chain(Some(0)).collect::<Vec<_>>();
if wname[..wname.len()-1].contains(&0) { return Err(Error { hresult: E_INVALIDARG, message: format!("configure_distribution({:?}) failed: distribution_name contained '\0'", distribution_name.as_ref()) }); }
let hr = unsafe { (self.WslConfigureDistribution)(wname.as_ptr(), default_uid, wsl_distribution_flags) };
if !SUCCEEDED(hr) { return Err(Error { hresult: hr, message: format!("WslConfigureDistribution({:?}, {}, {:?}) failed with HRESULT 0x{:08x}", distribution_name.as_ref(), default_uid, wsl_distribution_flags, hr) }); }
Ok(())
}
/// Retrieves the current configuration of a distribution registered with the Windows Subsystem for Linux (WSL).
///
/// ### Arguments
///
/// * `distribution_name` - Unique name representing a distribution (for example, "Fabrikam.Distro.10.01").
///
/// ### Returns
///
/// - `Err(Error)` - if `distribution_name` contained `'\0'` characters
/// - `Err(Error)` - if `distribution_name` didn't exist?
/// - `Err(Error)` - if [WslGetDistributionConfiguration] failed
/// - `Ok(Configuration { version, default_uid, flags, default_environment_variables })` - otherwise
///
/// ### See Also
///
/// - [WslGetDistributionConfiguration] - the underlying API
/// - [Configuration] - the returned struct
///
/// [WslGetDistributionConfiguration]: https://docs.microsoft.com/en-us/windows/win32/api/wslapi/nf-wslapi-wslgetdistributionconfiguration
pub fn get_distribution_configuration(&self, distribution_name: impl AsRef<OsStr>) -> Result<Configuration> {
let wname = distribution_name.as_ref().encode_wide().chain(Some(0)).collect::<Vec<_>>();
if wname[..wname.len()-1].contains(&0) { return Err(Error { hresult: E_INVALIDARG, message: format!("get_distribution_configuration({:?}, ...) failed: distribution_name contained '\0'", distribution_name.as_ref()) }); }
let mut cfg = Configuration::default();
let mut nvars = 0;
let hr = unsafe { (self.WslGetDistributionConfiguration)(wname.as_ptr(), &mut cfg.version, &mut cfg.default_uid, &mut cfg.flags, &mut cfg.default_environment_variables.array, &mut nvars) };
cfg.default_environment_variables.count = nvars.try_into().unwrap();
if !SUCCEEDED(hr) { return Err(Error { hresult: hr, message: format!("WslGetDistributionConfiguration({:?}, ...) failed with HRESULT 0x{:08x}", distribution_name.as_ref(), hr) }); }
Ok(cfg)
}
/// Launches an interactive Windows Subsystem for Linux (WSL) process in the context of a particular distribution.
/// This differs from [Library::launch] in that the end user will be able to interact with the newly-created process.
///
/// ### Arguments
///
/// * `distribution_name` - Unique name representing a distribution (for example, "Fabrikam.Distro.10.01").
/// * `command` - Command to execute. If no command is supplied, launches the default shell.
/// * `use_current_working_directory` - Governs whether or not the launched process should inherit
/// the calling process's working directory. If `false`, the process is started in the WSL
/// default user's home directory ("~").
///
/// ### Returns
///
/// - `Err(Error)` - if `distribution_name` contained `'\0'` characters
/// - `Err(Error)` - if `distribution_name` didn't exist?
/// - `Err(Error)` - if `command` contained `'\0'` characters
/// - `Err(Error)` - if [WslLaunchInteractive] otherwise failed
/// - `Ok(DWORD)` - the exit code of the process after it exits.
///
/// ### See Also
///
/// - [Library::launch] - non-interactive, programatic interaction
/// - [WslLaunchInteractive] - the underlying API
///
/// [Library::launch]: crate::Library::launch
/// [WslLaunchInteractive]: https://docs.microsoft.com/en-us/windows/win32/api/wslapi/nf-wslapi-wsllaunchinteractive
pub fn launch_interactive(&self, distribution_name: impl AsRef<OsStr>, command: impl AsRef<OsStr>, use_current_working_directory: bool) -> Result<DWORD> {
let wname = distribution_name.as_ref().encode_wide().chain(Some(0)).collect::<Vec<_>>();
let wcmd = command.as_ref().encode_wide().chain(Some(0)).collect::<Vec<_>>();
if wname[..wname.len()-1].contains(&0) { return Err(Error { hresult: E_INVALIDARG, message: format!("launch_interactive({:?}, {:?}, {}) failed: distribution_name contained '\0'", distribution_name.as_ref(), command.as_ref(), use_current_working_directory) }); }
if wcmd [..wcmd .len()-1].contains(&0) { return Err(Error { hresult: E_INVALIDARG, message: format!("launch_interactive({:?}, {:?}, {}) failed: command contained '\0'", distribution_name.as_ref(), command.as_ref(), use_current_working_directory) }); }
let mut exit_code = 0;
let hr = unsafe { (self.WslLaunchInteractive)(wname.as_ptr(), if command.as_ref().is_empty() { null_mut() } else { wcmd.as_ptr() }, use_current_working_directory as BOOL, &mut exit_code) };
if !SUCCEEDED(hr) { return Err(Error { hresult: hr, message: format!("WslLaunchInteractive({:?}, {:?}, {}) failed with HRESULT 0x{:08x}", distribution_name.as_ref(), command.as_ref(), use_current_working_directory, hr) }); }
Ok(exit_code)
}
/// Launches a Windows Subsystem for Linux (WSL) process in the context of a particular distribution.
///
/// ### Arguments
///
/// * `distribution_name` - Unique name representing a distribution (for example, "Fabrikam.Distro.10.01").
/// * `command` - Command to execute. If no command is supplied, launches the default shell.
/// * `use_current_working_directory` - Governs whether or not the launched process should inherit
/// the calling process's working directory. If `false`, the process is started in the WSL
/// default user's home directory ("~").
/// * `stdin` - Handle to use for **STDIN**.
/// * `stdout` - Handle to use for **STDOUT**.
/// * `stderr` - Handle to use for **STDERR**.
///
/// ### Returns
///
/// - `Err(Error)` - if `distribution_name` contained `'\0'` characters
/// - `Err(Error)` - if `distribution_name` didn't exist?
/// - `Err(Error)` - if `command` contained `'\0'` characters
/// - `Err(Error)` - if `stdin`, `stdout`, or `stderr` failed to convert to [Stdio]
/// - `Err(Error)` - if `stdin`, `stdout`, or `stderr` was an invalid handle for [WslLaunch]
/// - `Err(Error)` - if [WslLaunch] otherwise failed
/// - `Ok(Process)` - if the WSL process that launched successfully
///
/// ### See Also
///
/// - [Process]
/// - [Library::launch_interactive] - interactive, inherits the same console handles
/// - [WslLaunch] - the underlying API
///
/// [Library::launch_interactive]: #method.launch_interactive
/// [WslLaunch]: https://docs.microsoft.com/en-us/windows/win32/api/wslapi/nf-wslapi-wsllaunch
pub fn launch<I, O, E>(
&self,
distribution_name: impl AsRef<OsStr>,
command: impl AsRef<OsStr>,
use_current_working_directory: bool,
stdin: I,
stdout: O,
stderr: E,
) -> Result<Process> where
I : TryInto<Stdio>, I::Error : Display,
O : TryInto<Stdio>, O::Error : Display,
E : TryInto<Stdio>, E::Error : Display,
{
// https://github.com/microsoft/WSL-DistroLauncher/blob/540a593313f8abbc8ce3afe8ca98434e8a771798/DistroLauncher/DistributionInfo.cpp#L48
let wname = distribution_name.as_ref().encode_wide().chain(Some(0)).collect::<Vec<_>>();
let wcmd = command.as_ref().encode_wide().chain(Some(0)).collect::<Vec<_>>();
if wname[..wname.len()-1].contains(&0) { return Err(Error { hresult: E_INVALIDARG, message: format!("launch({:?}, {:?}, {}, ...) failed: distribution_name contained '\0'", distribution_name.as_ref(), command.as_ref(), use_current_working_directory) }); }
if wcmd [..wcmd .len()-1].contains(&0) { return Err(Error { hresult: E_INVALIDARG, message: format!("launch({:?}, {:?}, {}, ...) failed: command contained '\0'", distribution_name.as_ref(), command.as_ref(), use_current_working_directory) }); }
let stdin = stdin .try_into().map_err(|err| Error { hresult: E_INVALIDARG, message: format!("launch({:?}, {:?}, {}, ...) failed: failed to convert stdin: {}", distribution_name.as_ref(), command.as_ref(), use_current_working_directory, err) })?;
let stdout = stdout.try_into().map_err(|err| Error { hresult: E_INVALIDARG, message: format!("launch({:?}, {:?}, {}, ...) failed: failed to convert stdout: {}", distribution_name.as_ref(), command.as_ref(), use_current_working_directory, err) })?;
let stderr = stderr.try_into().map_err(|err| Error { hresult: E_INVALIDARG, message: format!("launch({:?}, {:?}, {}, ...) failed: failed to convert stderr: {}", distribution_name.as_ref(), command.as_ref(), use_current_working_directory, err) })?;
let mut handle = null_mut();
let hr = unsafe { (self.WslLaunch)(wname.as_ptr(), wcmd.as_ptr(), use_current_working_directory as BOOL, stdin.as_winapi_handle(), stdout.as_winapi_handle(), stderr.as_winapi_handle(), &mut handle) };
if !SUCCEEDED(hr) { return Err(Error { hresult: hr, message: format!("WslLaunch({:?}, {:?}, {}, ...) failed with HRESULT 0x{:08x}", distribution_name.as_ref(), command.as_ref(), use_current_working_directory, hr) }); }
Ok(Process { handle, stdin, stdout, stderr })
}
}