From 476981874b4f6d109772ba92204a5812152f7333 Mon Sep 17 00:00:00 2001 From: Sergii Bondarenko Date: Mon, 25 Dec 2017 20:21:33 +0400 Subject: [PATCH 01/13] #9298: Compute a correct path to the current WSL instance --- lib/vagrant/util/platform.rb | 45 +++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/util/platform.rb b/lib/vagrant/util/platform.rb index 9d581c527..fce7511d7 100644 --- a/lib/vagrant/util/platform.rb +++ b/lib/vagrant/util/platform.rb @@ -282,6 +282,27 @@ module Vagrant return @_platform end + def linux_distro + return @_linux_distro if defined?(@_linux_distro) + + @_linux_distro = nil + + if linux? + # A simplest way to get the Linux distribution name. + result = Subprocess.execute( + "python", + "-c", + "import platform;print(platform.linux_distribution()[0])" + ) + + if result.exit_code.zero? + @_linux_distro = result.stdout.chomp + end + end + + @_linux_distro + end + # Determine if given path is within the WSL rootfs. Returns # true if within the subsystem, or false if outside the subsystem. # @@ -291,6 +312,28 @@ module Vagrant wsl? && !path.to_s.downcase.start_with?("/mnt/") end + # Compute the path to rootfs of currently active WSL. + # + # @return [String] A path to rootfs of a current WSL instance. + def wsl_rootfs + return @_wsl_rootfs if defined?(@_wsl_rootfs) + + @_wsl_rootfs = nil + + if wsl? + # Handle WSL installation from Microsoft Store. + @_wsl_rootfs = PowerShell.execute_cmd('(Get-ChildItem "HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss" -Recurse | ForEach-Object { Get-ItemProperty $_.pspath } | Where-Object { $_.PackageFamilyName -eq ($(get-appxpackage).PackageFamilyName | findstr ' + linux_distro + ') }).BasePath') + end + + if @_wsl_rootfs.nil? + # Looks like WSL has been installed via "lxrun /install" which is deprecated. + @_wsl_rootfs = wsl_windows_appdata_local + '\\lxss' + else + # The path has been found in the registry, so append the directory with FS. + @_wsl_rootfs += '\\rootfs' + end + end + # Convert a WSL path to the local Windows path. This is useful # for conversion when calling out to Windows executables from # the WSL @@ -302,7 +345,7 @@ module Vagrant if wsl_path?(path) parts = path.split("/") parts.delete_if(&:empty?) - [wsl_windows_appdata_local, "lxss", *parts].join("\\") + [wsl_rootfs, *parts].join("\\") else path = path.to_s.sub("/mnt/", "") parts = path.split("/") From 72664eafec14c5bd283484a02e0b004088fe8c44 Mon Sep 17 00:00:00 2001 From: Sergii Bondarenko Date: Tue, 26 Dec 2017 00:07:46 +0400 Subject: [PATCH 02/13] #9298: Use a single-word distro name --- lib/vagrant/util/platform.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/vagrant/util/platform.rb b/lib/vagrant/util/platform.rb index fce7511d7..5ecb01220 100644 --- a/lib/vagrant/util/platform.rb +++ b/lib/vagrant/util/platform.rb @@ -290,9 +290,9 @@ module Vagrant if linux? # A simplest way to get the Linux distribution name. result = Subprocess.execute( - "python", - "-c", - "import platform;print(platform.linux_distribution()[0])" + "python", + "-c", + "import platform;print(platform.linux_distribution()[0].split(' ')[0])" ) if result.exit_code.zero? From 62740e2240e8af6431f7d1f32f24842e74b69197 Mon Sep 17 00:00:00 2001 From: Sergii Bondarenko Date: Wed, 27 Dec 2017 01:49:22 +0400 Subject: [PATCH 03/13] #9298: Increase stability of determination of a current WSL instance --- lib/vagrant/errors.rb | 4 +++ lib/vagrant/util/platform.rb | 55 ++++++++++++++++-------------------- templates/locales/en.yml | 4 +++ 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 3be2aae84..95fae400d 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -947,5 +947,9 @@ module Vagrant class WSLVirtualBoxWindowsAccessError < VagrantError error_key(:wsl_virtualbox_windows_access) end + + class WSLRootFsNotFoundError < VagrantError + error_key(:wsl_rootfs_not_found_error) + end end end diff --git a/lib/vagrant/util/platform.rb b/lib/vagrant/util/platform.rb index 5ecb01220..0402b6b00 100644 --- a/lib/vagrant/util/platform.rb +++ b/lib/vagrant/util/platform.rb @@ -282,27 +282,6 @@ module Vagrant return @_platform end - def linux_distro - return @_linux_distro if defined?(@_linux_distro) - - @_linux_distro = nil - - if linux? - # A simplest way to get the Linux distribution name. - result = Subprocess.execute( - "python", - "-c", - "import platform;print(platform.linux_distribution()[0].split(' ')[0])" - ) - - if result.exit_code.zero? - @_linux_distro = result.stdout.chomp - end - end - - @_linux_distro - end - # Determine if given path is within the WSL rootfs. Returns # true if within the subsystem, or false if outside the subsystem. # @@ -321,17 +300,33 @@ module Vagrant @_wsl_rootfs = nil if wsl? - # Handle WSL installation from Microsoft Store. - @_wsl_rootfs = PowerShell.execute_cmd('(Get-ChildItem "HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss" -Recurse | ForEach-Object { Get-ItemProperty $_.pspath } | Where-Object { $_.PackageFamilyName -eq ($(get-appxpackage).PackageFamilyName | findstr ' + linux_distro + ') }).BasePath') + PowerShell.execute_cmd('(Get-ChildItem HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss | ForEach-Object {Get-ItemProperty $_.PSPath}).BasePath').split(" ").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" + + begin + fs = Dir.open(path) + rescue Errno::EACCES + # 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. + @_wsl_rootfs = path + # You can create and simultaneously run multiple WSL instances, + # comment out the "break", run this script within each one and + # it'll return only single value. + break + else + fs.close + end + end + + if @_wsl_rootfs.nil? + raise Vagrant::Errors::WSLRootFsNotFoundError + end end - if @_wsl_rootfs.nil? - # Looks like WSL has been installed via "lxrun /install" which is deprecated. - @_wsl_rootfs = wsl_windows_appdata_local + '\\lxss' - else - # The path has been found in the registry, so append the directory with FS. - @_wsl_rootfs += '\\rootfs' - end + @_wsl_rootfs end # Convert a WSL path to the local Windows path. This is useful diff --git a/templates/locales/en.yml b/templates/locales/en.yml index aefee43aa..63295a30a 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1629,6 +1629,10 @@ en: Linux, please refer to the Vagrant documentation: https://www.vagrantup.com/docs/other/wsl.html + + wsl_rootfs_not_found_error: |- + Vagrant is unable to determine WSL instance it is currently in. If you see this + error then your WSL installation is incorrect. #------------------------------------------------------------------------------- # Translations for config validation errors #------------------------------------------------------------------------------- From a1211caa9c01f47615eb570a1ac97622459fd97f Mon Sep 17 00:00:00 2001 From: Sergii Bondarenko Date: Wed, 27 Dec 2017 14:59:34 +0400 Subject: [PATCH 04/13] #9298: Add debugging messages and explanations to the code --- lib/vagrant/util/platform.rb | 45 +++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/lib/vagrant/util/platform.rb b/lib/vagrant/util/platform.rb index 0402b6b00..0e8d56365 100644 --- a/lib/vagrant/util/platform.rb +++ b/lib/vagrant/util/platform.rb @@ -1,6 +1,8 @@ require "rbconfig" require "shellwords" +require "tempfile" require "tmpdir" +require "log4r" require "vagrant/util/subprocess" require "vagrant/util/powershell" @@ -300,30 +302,57 @@ module Vagrant @_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.debug("Querying installed WSL from Windows registry.") + PowerShell.execute_cmd('(Get-ChildItem HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss | ForEach-Object {Get-ItemProperty $_.PSPath}).BasePath').split(" ").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" + path = "/mnt/#{path[0, 1].downcase}#{path[2..-1].tr('\\', '/')}/rootfs" begin - fs = Dir.open(path) - rescue Errno::EACCES + # 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. + # For example we have three WSL instances: + # A -> C:\User\USER\AppData\Local|Packages\A\LocalState\rootfs + # B -> C:\User\USER\AppData\Local|Packages\B\LocalState\rootfs + # C -> C:\User\USER\AppData\Local|Packages\C\LocalState\rootfs + # 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| + # 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}") + end + rescue Errno::EACCES @_wsl_rootfs = path # You can create and simultaneously run multiple WSL instances, # comment out the "break", run this script within each one and # it'll return only single value. break - else - fs.close + rescue Errno::ENOENT + # Warn about data discrepancy between Winreg and file system + # states. For the sake of justice, it's worth mentioning that + # 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.") end + # All other exceptions have to be raised since they will mean + # something unpredictably terrible. end - if @_wsl_rootfs.nil? - raise Vagrant::Errors::WSLRootFsNotFoundError - end + marker.close! + + raise Vagrant::Errors::WSLRootFsNotFoundError if @_wsl_rootfs.nil? end @_wsl_rootfs From 6d08d1c5bfcd475652dc75190c3dc7c07a3e766f Mon Sep 17 00:00:00 2001 From: Sergii Bondarenko Date: Wed, 27 Dec 2017 15:16:12 +0400 Subject: [PATCH 05/13] #9298: Improve error message when WSL rootfs cannot be found --- templates/locales/en.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 63295a30a..4af7208c5 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1629,10 +1629,13 @@ en: Linux, please refer to the Vagrant documentation: https://www.vagrantup.com/docs/other/wsl.html - wsl_rootfs_not_found_error: |- - Vagrant is unable to determine WSL instance it is currently in. If you see this - error then your WSL installation is incorrect. + Vagrant is unable to determine WSL instance you are currently in because Windows + registry has no information about it at "HKCU:\Software\Microsoft\Windows\Current + Version\Lxss". + + It is recommended to destroy this WSL and do a clean installation from Microsoft + Store. #------------------------------------------------------------------------------- # Translations for config validation errors #------------------------------------------------------------------------------- From c5fdd47a56eb276c6a99372a74e16e1c4cae33e8 Mon Sep 17 00:00:00 2001 From: Sergii Bondarenko Date: Tue, 13 Feb 2018 17:19:11 +0400 Subject: [PATCH 06/13] #9298: Respect usernames with spaces --- lib/vagrant/util/platform.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vagrant/util/platform.rb b/lib/vagrant/util/platform.rb index 0e8d56365..10522ea05 100644 --- a/lib/vagrant/util/platform.rb +++ b/lib/vagrant/util/platform.rb @@ -308,7 +308,7 @@ module Vagrant logger.debug("Querying installed WSL from Windows registry.") - PowerShell.execute_cmd('(Get-ChildItem HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss | ForEach-Object {Get-ItemProperty $_.PSPath}).BasePath').split(" ").each do |path| + PowerShell.execute_cmd('(Get-ChildItem HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss | ForEach-Object {Get-ItemProperty $_.PSPath}).BasePath').split("\r\n").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" From 47a7f0817cfbd05500a9e68e3d836b0fdb45d178 Mon Sep 17 00:00:00 2001 From: Sergii Bondarenko Date: Tue, 13 Feb 2018 20:06:42 +0400 Subject: [PATCH 07/13] #9298: Try to fallback to "powershell.exe" on WSL if "powershell" is not available --- lib/vagrant/util/powershell.rb | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/lib/vagrant/util/powershell.rb b/lib/vagrant/util/powershell.rb index 62a8bc472..d685e0b56 100644 --- a/lib/vagrant/util/powershell.rb +++ b/lib/vagrant/util/powershell.rb @@ -13,12 +13,27 @@ module Vagrant MINIMUM_REQUIRED_VERSION = 3 LOGGER = Log4r::Logger.new("vagrant::util::powershell") + # @return [String|nil] a powershell executable, depending on environment + def self.executable + if !defined?(@_powershell_executable) + @_powershell_executable = "powershell" + + # Try to use WSL interoperability if PowerShell is not symlinked to + # the container. + if Which.which(@_powershell_executable).nil? && Platform.wsl? + @_powershell_executable += ".exe" + + if Which.which(@_powershell_executable).nil? + @_powershell_executable = nil + end + end + end + @_powershell_executable + end + # @return [Boolean] powershell executable available on PATH def self.available? - if !defined?(@_powershell_available) - @_powershell_available = !!Which.which("powershell") - end - @_powershell_available + !executable.nil? end # Execute a powershell script. @@ -27,12 +42,11 @@ module Vagrant # @return [Subprocess::Result] def self.execute(path, *args, **opts, &block) validate_install! - if opts.delete(:sudo) || opts.delete(:runas) powerup_command(path, args, opts) else command = [ - "powershell", + executable, "-NoLogo", "-NoProfile", "-NonInteractive", @@ -57,7 +71,7 @@ module Vagrant def self.execute_cmd(command) validate_install! c = [ - "powershell", + executable, "-NoLogo", "-NoProfile", "-NonInteractive", @@ -77,7 +91,7 @@ module Vagrant def self.version if !defined?(@_powershell_version) command = [ - "powershell", + executable, "-NoLogo", "-NoProfile", "-NonInteractive", From cc82f9661815f99372c69a1728ac6ee9254bd9f3 Mon Sep 17 00:00:00 2001 From: Sergii Bondarenko Date: Tue, 13 Feb 2018 20:12:14 +0400 Subject: [PATCH 08/13] #9298: Fix typos in comments to the code --- lib/vagrant/util/platform.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/vagrant/util/platform.rb b/lib/vagrant/util/platform.rb index 10522ea05..b8c3700ec 100644 --- a/lib/vagrant/util/platform.rb +++ b/lib/vagrant/util/platform.rb @@ -320,9 +320,9 @@ module Vagrant # within itself despite all others are available. That's the # hacky way we're using to determine current instance. # For example we have three WSL instances: - # A -> C:\User\USER\AppData\Local|Packages\A\LocalState\rootfs - # B -> C:\User\USER\AppData\Local|Packages\B\LocalState\rootfs - # C -> C:\User\USER\AppData\Local|Packages\C\LocalState\rootfs + # A -> C:\User\USER\AppData\Local\Packages\A\LocalState\rootfs + # B -> C:\User\USER\AppData\Local\Packages\B\LocalState\rootfs + # C -> C:\User\USER\AppData\Local\Packages\C\LocalState\rootfs # 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. From 5f0d16a0e993c017edda5d689f68c5addb83e4e1 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Tue, 27 Feb 2018 17:27:36 -0800 Subject: [PATCH 09/13] 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 From 48bb306d1715b5ea7bc5757dac84ada8b5ac712b Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Tue, 27 Feb 2018 17:41:14 -0800 Subject: [PATCH 10/13] Update the wording to not assume latest WSL features --- templates/locales/en.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 4af7208c5..d6c78cacd 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1630,12 +1630,9 @@ en: https://www.vagrantup.com/docs/other/wsl.html wsl_rootfs_not_found_error: |- - Vagrant is unable to determine WSL instance you are currently in because Windows - registry has no information about it at "HKCU:\Software\Microsoft\Windows\Current - Version\Lxss". - - It is recommended to destroy this WSL and do a clean installation from Microsoft - Store. + Vagrant is unable to determine the location of this instance of the Windows + Subsystem for Linux. If this error persists it may be resolved by destroying + this subsystem and installing it again. #------------------------------------------------------------------------------- # Translations for config validation errors #------------------------------------------------------------------------------- From bab940332c985bb54d1ca0de77061971471624ea Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 28 Feb 2018 10:05:14 -0800 Subject: [PATCH 11/13] Add a custom path location to ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index eab642c84..7df3f0de4 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ tags test/tmp/ vendor/ /exec +.ruby-bundle # Documentation _site/* From d6f4eef698d2aea34f7b74c393eba9764c8bf236 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 28 Feb 2018 10:05:33 -0800 Subject: [PATCH 12/13] Don't include test files within gem package --- vagrant.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/vagrant.gemspec b/vagrant.gemspec index d26b32adf..edc12f032 100644 --- a/vagrant.gemspec +++ b/vagrant.gemspec @@ -55,6 +55,7 @@ Gem::Specification.new do |s| all_files = Dir.chdir(root_path) { Dir.glob("**/{*,.*}") } all_files.reject! { |file| [".", ".."].include?(File.basename(file)) } all_files.reject! { |file| file.start_with?("website/") } + all_files.reject! { |file| file.start_with?("test/") } gitignore_path = File.join(root_path, ".gitignore") gitignore = File.readlines(gitignore_path) gitignore.map! { |line| line.chomp.strip } From 8b7e501e50ad15a4da2b6d4bff47236e44f4f48a Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 28 Feb 2018 10:06:15 -0800 Subject: [PATCH 13/13] Handle pathing in lxrun generated WSL instances better. --- lib/vagrant/util/platform.rb | 18 ++++++-- test/unit/vagrant/util/platform_test.rb | 61 ++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/lib/vagrant/util/platform.rb b/lib/vagrant/util/platform.rb index c8f8fa7b9..7f0d6f039 100644 --- a/lib/vagrant/util/platform.rb +++ b/lib/vagrant/util/platform.rb @@ -305,7 +305,8 @@ module Vagrant logger = Log4r::Logger.new("vagrant::util::platform::wsl") # Check for lxrun installation first - paths = [[wsl_windows_appdata_local, "lxss"].join("\\")] + lxrun_path = [wsl_windows_appdata_local, "lxss"].join("\\") + paths = [lxrun_path] logger.debug("checking registry for WSL installation path") paths += PowerShell.execute_cmd( @@ -363,6 +364,11 @@ module Vagrant raise Vagrant::Errors::WSLRootFsNotFoundError if @_wsl_rootfs.nil? end + # Attach the rootfs leaf to the path + if @_wsl_rootfs != lxrun_path + @_wsl_rootfs = "#{@_wsl_rootfs}\\rootfs" + end + logger.debug("detected `#{@_wsl_rootfs}` as current WSL instance") @_wsl_rootfs @@ -375,11 +381,17 @@ module Vagrant # @param [String, Pathname] path Path to convert # @return [String] def wsl_to_windows_path(path) - if wsl? && wsl_windows_access? + if wsl? && wsl_windows_access? && !path.match(/^[a-zA-Z]:/) if wsl_path?(path) parts = path.split("/") parts.delete_if(&:empty?) - [wsl_rootfs, *parts].join("\\") + root_path = wsl_rootfs + # lxrun splits home separate so we need to account + # for it's specialness here when we build the path + if root_path.end_with?("lxss") && parts.first != "home" + root_path = "#{root_path}\\rootfs" + end + [root_path, *parts].join("\\") else path = path.to_s.sub("/mnt/", "") parts = path.split("/") diff --git a/test/unit/vagrant/util/platform_test.rb b/test/unit/vagrant/util/platform_test.rb index ba3cf17be..8b4046188 100644 --- a/test/unit/vagrant/util/platform_test.rb +++ b/test/unit/vagrant/util/platform_test.rb @@ -281,12 +281,69 @@ describe Vagrant::Util::Platform do 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) + expect(subject.wsl_rootfs).to start_with(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) + expect(subject.wsl_rootfs).to start_with(matching_path) + end + end + end + end + + describe ".wsl_to_windows_path" do + let(:path){ "/home/vagrant/test" } + + context "when not within WSL" do + before{ allow(subject).to receive(:wsl?).and_return(false) } + + it "should return the path unmodified" do + expect(subject.wsl_to_windows_path(path)).to eq(path) + end + end + + context "when within WSL" do + before{ allow(subject).to receive(:wsl?).and_return(true) } + + context "when windows access is not enabled" do + before{ allow(subject).to receive(:wsl_windows_access?).and_return(false) } + + it "should return the path unmodified" do + expect(subject.wsl_to_windows_path(path)).to eq(path) + end + end + + context "when windows access is enabled" do + let(:rootfs_path){ "C:\\WSL\\rootfs" } + + before do + allow(subject).to receive(:wsl_windows_access?).and_return(true) + allow(subject).to receive(:wsl_rootfs).and_return(rootfs_path) + end + + it "should generate expanded path when within WSL" do + expect(subject.wsl_to_windows_path(path)).to eq("#{rootfs_path}#{path.gsub("/", "\\")}") + end + + it "should generate direct path when outside the WSL" do + expect(subject.wsl_to_windows_path("/mnt/c/vagrant")).to eq("c:\\vagrant") + end + + it "should not modify path when already in windows format" do + expect(subject.wsl_to_windows_path("C:\\vagrant")).to eq("C:\\vagrant") + end + + context "when within lxrun generated WSL instance" do + let(:rootfs_path){ "C:\\WSL\\lxss" } + + it "should not include rootfs when accessing home" do + expect(subject.wsl_to_windows_path("/home/vagrant")).not_to include("rootfs") + end + + it "should include rootfs when accessing non-home path" do + expect(subject.wsl_to_windows_path("/tmp/test")).to include("rootfs") + end end end end