Include support for lxrun generated install

Adds compatibility support for legacy lxrun generated WSL installation
which is a single install at a known path location. This allows earlier
versions of Windows 10 to continue working as expected while still
supporting the recent updates allowing for multiple instances.
This commit is contained in:
Chris Roberts 2018-02-27 17:27:36 -08:00
parent cc82f96618
commit 5f0d16a0e9
2 changed files with 97 additions and 24 deletions

View File

@ -299,23 +299,28 @@ module Vagrant
def wsl_rootfs
return @_wsl_rootfs if defined?(@_wsl_rootfs)
@_wsl_rootfs = nil
if wsl?
# Mark our filesystem with a temporary file having an unique name.
marker = Tempfile.new(Time.now.to_i.to_s)
logger = Log4r::Logger.new("vagrant::util::platfowm::wsl")
logger = Log4r::Logger.new("vagrant::util::platform::wsl")
logger.debug("Querying installed WSL from Windows registry.")
# Check for lxrun installation first
paths = [[wsl_windows_appdata_local, "lxss"].join("\\")]
PowerShell.execute_cmd('(Get-ChildItem HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss | ForEach-Object {Get-ItemProperty $_.PSPath}).BasePath').split("\r\n").each do |path|
logger.debug("checking registry for WSL installation path")
paths += PowerShell.execute_cmd(
'(Get-ChildItem HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss ' \
'| ForEach-Object {Get-ItemProperty $_.PSPath}).BasePath').to_s.split("\r\n").map(&:strip)
paths.delete_if{|path| path.to_s.empty?}
paths.each do |path|
# Lowercase the drive letter, skip the next symbol (which is a
# colon from a Windows path) and convert path to UNIX style.
path = "/mnt/#{path[0, 1].downcase}#{path[2..-1].tr('\\', '/')}/rootfs"
check_path = "/mnt/#{path[0, 1].downcase}#{path[2..-1].tr('\\', '/')}/rootfs"
logger.debug("checking `#{path}` for current WSL instance")
begin
# https://blogs.msdn.microsoft.com/wsl/2016/06/15/wsl-file-system-support
logger.debug("Checking whether the \"#{path}\" is a root VolFS mount of current WSL instance.")
# Current WSL instance doesn't have an access to its mount from
# within itself despite all others are available. That's the
# hacky way we're using to determine current instance.
@ -326,11 +331,14 @@ module Vagrant
# If we're in "A" WSL at the moment, then its path will not be
# accessible since it's mounted for exactly the instance we're
# in. All others can be opened.
Dir.open(path) do |fs|
Dir.open(check_path) do |fs|
# A fallback for a case if our trick will stop working. For
# that we've created a temporary file with an unique name in
# a current WSL and now seeking it among all WSL.
@_wsl_rootfs = path if File.exist?("#{fs.path}/#{marker.path}")
if File.exist?("#{fs.path}/#{marker.path}")
@_wsl_rootfs = path
break
end
end
rescue Errno::EACCES
@_wsl_rootfs = path
@ -344,7 +352,7 @@ module Vagrant
# it is possible only when someone will manually break WSL by
# removing a directory of its base path (kinda "stupid WSL
# uninstallation by removing hidden and system directory").
logger.warn("Windows registry has an information about WSL instance with the \"#{path}\" base path that is no longer exist or broken.")
logger.warn("WSL instance at `#{path} is broken or no longer exists")
end
# All other exceptions have to be raised since they will mean
# something unpredictably terrible.
@ -355,6 +363,8 @@ module Vagrant
raise Vagrant::Errors::WSLRootFsNotFoundError if @_wsl_rootfs.nil?
end
logger.debug("detected `#{@_wsl_rootfs}` as current WSL instance")
@_wsl_rootfs
end

View File

@ -4,8 +4,7 @@ require "vagrant/util/platform"
describe Vagrant::Util::Platform do
include_context "unit"
after{ described_class.reset! }
subject { described_class }
describe "#cygwin_path" do
@ -55,11 +54,6 @@ describe Vagrant::Util::Platform do
describe "#cygwin?" do
before do
allow(subject).to receive(:platform).and_return("test")
described_class.reset!
end
after do
described_class.reset!
end
around do |example|
@ -99,11 +93,6 @@ describe Vagrant::Util::Platform do
describe "#msys?" do
before do
allow(subject).to receive(:platform).and_return("test")
described_class.reset!
end
after do
described_class.reset!
end
around do |example|
@ -162,7 +151,6 @@ describe Vagrant::Util::Platform do
describe ".systemd?" do
before{ allow(subject).to receive(:windows?).and_return(false) }
after{ subject.reset! }
context "on windows" do
before{ expect(subject).to receive(:windows?).and_return(true) }
@ -223,10 +211,85 @@ describe Vagrant::Util::Platform do
end
it "should return false if disabled" do
Vagrant::Util::Platform.reset!
allow(Vagrant::Util::PowerShell).to receive(:execute_cmd).and_return('Disabled')
expect(Vagrant::Util::Platform.windows_hyperv_enabled?).to be_falsey
end
end
context "within the WSL" do
before{ allow(subject).to receive(:wsl?).and_return(true) }
describe ".wsl_path?" do
it "should return true when path is not within /mnt" do
expect(subject.wsl_path?("/tmp")).to be(true)
end
it "should return false when path is within /mnt" do
expect(subject.wsl_path?("/mnt/c")).to be false
end
end
describe ".wsl_rootfs" do
let(:appdata_path){ "C:\\Custom\\Path" }
let(:registry_paths){ nil }
before do
allow(subject).to receive(:wsl_windows_appdata_local).and_return(appdata_path)
allow(Tempfile).to receive(:new).and_return(double("tempfile", path: "file.path", close!: true))
allow(Vagrant::Util::PowerShell).to receive(:execute_cmd).and_return(registry_paths)
end
context "when no instance information is in the registry" do
before do
expect(Dir).to receive(:open).with(/.*Custom.*Path.*/).and_yield(double("path", path: appdata_path))
expect(File).to receive(:exist?).and_return(true)
end
it "should only check the lxrun path" do
expect(subject.wsl_rootfs).to include(appdata_path)
end
end
context "with instance information in the registry" do
let(:registry_paths) { ["C:\\Path1", "C:\\Path2"].join("\r\n") }
before do
allow(Dir).to receive(:open).and_yield(double("path", path: appdata_path))
allow(File).to receive(:exist?).and_return(false)
end
context "when no matches are detected" do
it "should check all paths given" do
expect(Dir).to receive(:open).and_yield(double("path", path: appdata_path)).exactly(3).times
expect(File).to receive(:exist?).and_return(false).exactly(3).times
expect{ subject.wsl_rootfs }.to raise_error(Vagrant::Errors::WSLRootFsNotFoundError)
end
it "should raise not found error" do
expect{ subject.wsl_rootfs }.to raise_error(Vagrant::Errors::WSLRootFsNotFoundError)
end
end
context "when file marker match found" do
let(:matching_path){ registry_paths.split("\r\n").last }
let(:matching_part){ matching_path.split("\\").last }
before do
allow(File).to receive(:exist?).with(/#{matching_part}/).and_return(true)
end
it "should return the matching path" do
expect(Dir).to receive(:open).with(/#{matching_part}/).and_yield(double("path", path: matching_part))
expect(subject.wsl_rootfs).to eq(matching_path)
end
it "should return matching path when access error encountered" do
expect(Dir).to receive(:open).with(/#{matching_part}/).and_raise(Errno::EACCES)
expect(subject.wsl_rootfs).to eq(matching_path)
end
end
end
end
end
end