From 5f0d16a0e993c017edda5d689f68c5addb83e4e1 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Tue, 27 Feb 2018 17:27:36 -0800 Subject: [PATCH] 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. --- lib/vagrant/util/platform.rb | 30 +++++--- test/unit/vagrant/util/platform_test.rb | 91 +++++++++++++++++++++---- 2 files changed, 97 insertions(+), 24 deletions(-) diff --git a/lib/vagrant/util/platform.rb b/lib/vagrant/util/platform.rb index b8c3700ec..c8f8fa7b9 100644 --- a/lib/vagrant/util/platform.rb +++ b/lib/vagrant/util/platform.rb @@ -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 diff --git a/test/unit/vagrant/util/platform_test.rb b/test/unit/vagrant/util/platform_test.rb index c71cf0ab1..ba3cf17be 100644 --- a/test/unit/vagrant/util/platform_test.rb +++ b/test/unit/vagrant/util/platform_test.rb @@ -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