Packages
nerves
1.3.4
1.15.0
1.14.3
1.14.2
1.14.1
1.14.0
1.13.2
1.13.1
1.13.0
1.12.0
1.11.3
1.11.2
1.11.1
1.11.0
1.10.5
1.10.4
1.10.3
1.10.2
1.10.1
1.10.0
1.9.3
1.9.2
retired
1.9.1
1.9.0
1.8.0
1.7.17
1.7.16
1.7.15
1.7.14
retired
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
retired
1.7.1
retired
1.7.0
retired
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.1
1.2.0
1.1.1
1.1.0
1.0.1
1.0.0
1.0.0-rc.2
1.0.0-rc.1
1.0.0-rc.0
0.11.0
0.10.1
0.10.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.3
0.8.2
0.8.1
0.8.0
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.4.0-rc.0
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
Craft and deploy bulletproof embedded software
Current section
Files
Jump to
Current section
Files
lib/nerves/utils/wsl.ex
defmodule Nerves.Utils.WSL do
@moduledoc """
This module contains utility functions to assist in detecting a Windows
Subsystem for Linux environment as well as functions to convert paths between
the Windows host and Linux.
"""
@doc """
Returns a two item tuple where the first item is a command and the second is
the argument list to run a powershell command as administrator in Windows
"""
def admin_powershell_command(command, args) do
{
"powershell.exe",
[
"-Command",
"Start-Process #{command} -Verb runAs -Wait -ArgumentList \"#{args}\""
]
}
end
@doc """
Returns true if inside a WSL shell environment
"""
@spec running_on_wsl?(String.t()) :: boolean
def running_on_wsl?(osrelease_path \\ "/proc/sys/kernel/osrelease") do
with true <- File.exists?(osrelease_path),
{content, _} <- System.cmd("cat", [osrelease_path]) do
Regex.match?(~r/Microsoft/, content)
else
_ ->
false
end
end
@doc """
Gets a list of fwup devices on a Windows host. This function can be run from
within WSL, as it runs a powershell command to get the list and writes it to a
temporary file that WSL can access.
"""
def get_fwup_devices() do
{win_path, _} = make_file_accessible("fwup_devs.txt", running_on_wsl?(), has_wslpath?())
{_, wsl_path} = get_wsl_paths(win_path, has_wslpath?())
powershell_args = "fwup.exe -D | set-content -encoding UTF8 #{win_path}"
with {command, args} <- admin_powershell_command("powershell.exe", powershell_args),
{"", 0} <- System.cmd(command, args),
{:ok, devs} <- File.read(wsl_path) do
devs =
Regex.replace(~r/[\x{200B}\x{200C}\x{200D}\x{FEFF}]/u, devs, "")
|> String.replace("\r", "")
File.rm(wsl_path)
{devs, 0}
else
{:error, :enoent} ->
# fwup didn't find any devices and no tmp file was generated
# behave as fwup does normally and return an empty string result
{"", 0}
error ->
error
end
end
@doc """
Returns true if the WSL utility `wslpath` is available
"""
@spec has_wslpath?() :: boolean
def has_wslpath?() do
System.find_executable("wslpath") != nil
end
@doc """
Returns true if the path is accessible in Windows
"""
@spec path_accessible_in_windows?(String.t(), boolean) :: boolean
def path_accessible_in_windows?(file, _use_wslpath = true) do
{_path, exitcode} = System.cmd("wslpath", ["-w", "-a", file], stderr_to_stdout: true)
exitcode == 0
end
def path_accessible_in_windows?(file, _use_wslpath) do
Regex.match?(~r/(\/mnt\/\w{1})\//, file)
end
@doc """
Returns a path to the base file name a temporary location in Windows
"""
@spec get_temp_file_location(String.t()) :: String.t()
def get_temp_file_location(file) do
{win_path, 0} = System.cmd("cmd.exe", ["/c", "echo %TEMP%"])
"#{String.trim(win_path)}\\#{Path.basename(file)}"
end
@doc """
Returns true when the path starts with a drive letter, colon and either single backslash or double backslash
"""
@spec valid_windows_path?(String.t()) :: boolean
def valid_windows_path?(path) do
# Match <drive_letter>: then \ or \\ and then one or more characters except line breaks
Regex.match?(~r/(^\w{1}:)(\\\\|\\)(.+)/, path)
end
@doc """
Returns true if the path is not a Windows path
"""
@spec valid_wsl_path?(String.t()) :: boolean
def valid_wsl_path?(path) do
valid_windows_path?(path) === false
end
@doc """
Returns a two item tuple containing the Windows host path for a file and its WSL counterpart.
If the path is not available in either Windows or WSL, nil will replace the item
## Examples
iex> Nerves.Utils.WSL.get_wsl_paths("mix.exs", Nerves.Utils.WSL.has_wslpath?())
{"C:\\Users\\username\\src\\nerves\\mix.exs",
"/mnt/c/Users/username/src/nerves/mix.exs"}
"""
@spec get_wsl_paths(String.t(), boolean) :: {String.t() | nil, String.t() | nil}
def get_wsl_paths(file, _use_wslpath = true) do
# Use wslpath, available from Windows 10 1803
# https://superuser.com/questions/1113385/convert-windows-path-for-windows-ubuntu-bash
# -a force result to absolute path format
# -u translate from a Windows path to a WSL path (default)
# -w translate from a WSL path to a Windows path
# -m translate from a WSL path to a Windows path, with ‘/’ instead of ‘\\’
win_path =
if valid_windows_path?(file) do
file
else
execute_wslpath(file, ["-w", "-a"])
end
wsl_path =
if valid_wsl_path?(file) do
Path.expand(file)
else
execute_wslpath(file, ["-u", "-a"])
end
{win_path, wsl_path}
end
def get_wsl_paths(file, _use_wslpath) do
# Maintain support for Windows builds before 1803
fullpath =
if valid_wsl_path?(file) do
Path.expand(file)
else
file
end
# Check if the full path is accessible form Windows
win_path =
if Regex.match?(~r/(\/mnt\/\w{1})\//, fullpath) do
# extract drive letter from path
%{"drive" => drive_letter} = Regex.named_captures(~r/\/mnt\/(?<drive>\w{1})\//, fullpath)
# replace /mnt/<drive_letter>/ with windows version C:/
win_path =
Regex.replace(~r/(\/mnt\/\w{1})\//, fullpath, "#{String.upcase(drive_letter)}:/")
# replace forward slashes with backslashes
Regex.replace(~r/\//, win_path, "\\\\")
else
# If path is already a windows path, return it
if valid_windows_path?(fullpath) do
fullpath
else
nil
end
end
# Check if full path is a windows path
wsl_path =
if valid_windows_path?(fullpath) do
# extract drive letter
%{"drive" => drive_letter} = Regex.named_captures(~r/(?<drive>^.{1}):/, fullpath)
# replace <drive_letter>: with /mnt/<drive_letter>
wsl_path = Regex.replace(~r/^.{1}:/, fullpath, "/mnt/#{String.downcase(drive_letter)}")
# replace \\ or \ with forward slashes
Regex.replace(~r/\\\\|\\/, wsl_path, "/")
else
# if we are already a wsl path just return it
fullpath
end
{win_path, wsl_path}
end
@doc """
Executes wslpath with the file and arguments.
When a valid WSL path is passed through to `wslpath` asking for a
valid path an "Invalid argument" error is returned. This function
catches this error and returns the valid path.
"""
@spec execute_wslpath(String.t(), list) :: String.t() | nil
def execute_wslpath(file, arguments) do
with {path, 0} <- System.cmd("wslpath", arguments ++ [file], stderr_to_stdout: true) do
String.trim(path)
else
{error, _} ->
if String.contains?(error, "Invalid argument") do
Path.expand(file)
else
nil
end
end
end
@doc """
Returns an item tuple with the Windows accessible path and whether the path is a temporary location or original location
"""
@spec make_file_accessible(String.t(), boolean, boolean) ::
{String.t(), :original_location} | {String.t(), :temporary_location}
def make_file_accessible(file, _is_wsl = true, has_wslpath) do
if path_accessible_in_windows?(file, has_wslpath) do
{win_path, _wsl_path} = get_wsl_paths(file, has_wslpath)
{win_path, :original_location}
else
# Create a temporary .fw file that fwup.exe is able to access
temp_file_location = get_temp_file_location(file)
{win_path, wsl_path} = get_wsl_paths(temp_file_location, has_wslpath)
File.copy(file, wsl_path)
{win_path, :temporary_location}
end
end
def make_file_accessible(file, _is_wsl, _has_wslpath) do
{file, :original_location}
end
@doc """
If the file was created in a temporary location, get the WSL path and delete it. Otherwise return `:ok`
"""
@spec cleanup_file(String.t(), :temporary_location | :original_location) :: :ok | {:error, atom}
def cleanup_file(file, :temporary_location) do
{_win_path, wsl_path} = get_wsl_paths(file, has_wslpath?())
File.rm(wsl_path)
end
def cleanup_file(_, _), do: :ok
end