diff --git a/lib/vagrant/util/platform.rb b/lib/vagrant/util/platform.rb index 789d61ece..67a438578 100644 --- a/lib/vagrant/util/platform.rb +++ b/lib/vagrant/util/platform.rb @@ -483,6 +483,41 @@ module Vagrant path.to_s.start_with?(wsl_windows_accessible_path.to_s) end + # Mount pattern for extracting local mount information + MOUNT_PATTERN = /^(?.+?) on (?.+?) type (?.+?) \((?.+)\)/.freeze + + # Get list of local mount paths that are DrvFs file systems + # + # @return [Array] + def wsl_drvfs_mounts + if !defined?(@_wsl_drvfs_mounts) + @_wsl_drvfs_mounts = [] + if wsl? + result = Util::Subprocess.execute("mount") + result.stdout.each_line do |line| + info = line.match(MOUNT_PATTERN) + if info && info[:type] == "drvfs" + @_wsl_drvfs_mounts << info[:mount] + end + end + end + end + @_wsl_drvfs_mounts + end + + # Check if given path is located on DrvFs file system + # + # @param [String, Pathname] path Path to check + # @return [Boolean] + def wsl_drvfs_path?(path) + if wsl? + wsl_drvfs_mounts.each do |mount_path| + return true if path.to_s.start_with?(mount_path) + end + end + false + end + # If running within the Windows Subsystem for Linux, this will provide # simple setup to allow sharing of the user's VAGRANT_HOME directory # within the subsystem diff --git a/plugins/kernel_v2/config/vm.rb b/plugins/kernel_v2/config/vm.rb index b257ca97a..2214f42a5 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -3,6 +3,7 @@ require "securerandom" require "set" require "vagrant" +require "vagrant/action/builtin/mixin_synced_folders" require "vagrant/config/v2/util" require "vagrant/util/platform" require "vagrant/util/presence" @@ -757,6 +758,33 @@ module VagrantPlugins end end + # If running from the Windows Subsystem for Linux, validate that configured + # hostpaths for synced folders are on DrvFs file systems, or the synced + # folder implementation explicitly supports non-DrvFs file system types + # within the WSL + if Vagrant::Util::Platform.wsl? + # Create a helper that will with the synced folders mixin + # from the builtin action to get the correct implementation + # to be used for each folder + sf_helper = Class.new do + include Vagrant::Action::Builtin::MixinSyncedFolders + end.new + folders = sf_helper.synced_folders(machine, config: self) + folders.each do |impl_name, data| + data.each do |_, fs| + hostpath = File.expand_path(fs[:hostpath], machine.env.root_path) + if !Vagrant::Util::Platform.wsl_drvfs_path?(hostpath) + sf_klass = sf_helper.plugins[impl_name.to_sym].first + if sf_klass.respond_to?(:wsl_allow_non_drvfs?) && sf_klass.wsl_allow_non_drvfs? + next + end + errors["vm"] << I18n.t("vagrant.config.vm.shared_folder_wsl_not_drvfs", + path: fs[:hostpath]) + end + end + end + end + # Validate sub-VMs if there are any @__defined_vms.each do |name, _| if name =~ /[\[\]\{\}\/]/ diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 24ce2391f..8b102a8e8 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1794,6 +1794,10 @@ en: be an array of options. shared_folder_requires_guestpath_or_name: |- Shared folder options must include a guestpath and/or name. + shared_folder_wsl_not_drvfs: |- + The host path of the shared folder is not supported from WSL. Host + path of the shared folder must be located on a file system with + DrvFs type. Host path: %{path} #------------------------------------------------------------------------------- # Translations for commands. e.g. `vagrant x` diff --git a/test/unit/plugins/kernel_v2/config/vm_test.rb b/test/unit/plugins/kernel_v2/config/vm_test.rb index 29919d672..f88d1329c 100644 --- a/test/unit/plugins/kernel_v2/config/vm_test.rb +++ b/test/unit/plugins/kernel_v2/config/vm_test.rb @@ -589,6 +589,79 @@ describe VagrantPlugins::Kernel_V2::VMConfig do sf = subject.synced_folders expect(sf["my-vagrant-folder"][:guestpath]).to be_nil end + + context "WSL host paths" do + let(:valid_path){ "/mnt/c/path" } + let(:invalid_path){ "/home/vagrant/path" } + let(:synced_folder_impl){ double("synced_folder_impl", new: double("synced_folder_inst", usable?: true)) } + let(:fs_config){ double("fs_config", vm: double("fs_vm", allowed_synced_folder_types: nil)) } + let(:plugin){ double("plugin", manager: manager) } + let(:manager){ double("manager", synced_folders: {sf_impl: [synced_folder_impl, 1]}) } + + let(:stub_pathname){ double("stub_pathname", directory?: true, relative?: false) } + + before do + allow(Pathname).to receive(:new).and_return(stub_pathname) + allow(stub_pathname).to receive(:expand_path).and_return(stub_pathname) + allow(Vagrant::Util::Platform).to receive(:wsl?).and_return(true) + allow(Vagrant::Util::Platform).to receive(:wsl_drvfs_path?).with(valid_path).and_return(true) + allow(Vagrant::Util::Platform).to receive(:wsl_drvfs_path?).with(invalid_path).and_return(false) + allow(machine).to receive(:config).and_return(fs_config) + allow(Vagrant).to receive(:plugin).with("2").and_return(plugin) + subject.synced_folder(".", "/vagrant", disabled: true) + end + + it "is valid when located on DrvFs" do + subject.synced_folder(valid_path, "/guest/path") + subject.finalize! + assert_valid + end + + it "is invalid when not located on DrvFs" do + subject.synced_folder(invalid_path, "/guest/path") + subject.finalize! + assert_invalid + end + + context "when synced folder defines support for non-DrvFs" do + let(:support_nondrvfs){ true } + + before do + allow(synced_folder_impl).to receive(:respond_to?).with(:wsl_allow_non_drvfs?).and_return(true) + allow(synced_folder_impl).to receive(:wsl_allow_non_drvfs?).and_return(support_nondrvfs) + end + + context "and is supported" do + it "is valid when located on DrvFs" do + subject.synced_folder(valid_path, "/guest/path") + subject.finalize! + assert_valid + end + + it "is valid when not located on DrvFs" do + subject.synced_folder(invalid_path, "/guest/path") + subject.finalize! + assert_valid + end + end + + context "and is not supported" do + let(:support_nondrvfs){ false } + + it "is valid when located on DrvFs" do + subject.synced_folder(valid_path, "/guest/path") + subject.finalize! + assert_valid + end + + it "is invalid when not located on DrvFs" do + subject.synced_folder(invalid_path, "/guest/path") + subject.finalize! + assert_invalid + end + end + end + end end describe "#usable_port_range" do diff --git a/test/unit/vagrant/util/platform_test.rb b/test/unit/vagrant/util/platform_test.rb index f43afd1d8..533f4461d 100644 --- a/test/unit/vagrant/util/platform_test.rb +++ b/test/unit/vagrant/util/platform_test.rb @@ -357,5 +357,53 @@ describe Vagrant::Util::Platform do end end end + + describe ".wsl_drvfs_mounts" do + let(:mount_output) { <<-EOF +rootfs on / type lxfs (rw,noatime) +sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,noatime) +proc on /proc type proc (rw,nosuid,nodev,noexec,noatime) +none on /dev type tmpfs (rw,noatime,mode=755) +devpts on /dev/pts type devpts (rw,nosuid,noexec,noatime) +none on /run type tmpfs (rw,nosuid,noexec,noatime,mode=755) +none on /run/lock type tmpfs (rw,nosuid,nodev,noexec,noatime) +none on /run/shm type tmpfs (rw,nosuid,nodev,noatime) +none on /run/user type tmpfs (rw,nosuid,nodev,noexec,noatime,mode=755) +binfmt_misc on /proc/sys/fs/binfmt_misc type binfmt_misc (rw,noatime) +C: on /mnt/c type drvfs (rw,noatime) +EOF + } + + before do + expect(Vagrant::Util::Subprocess).to receive(:execute).with("mount"). + and_return(Vagrant::Util::Subprocess::Result.new(0, mount_output, "")) + end + + it "should locate DrvFs mount path" do + expect(subject.wsl_drvfs_mounts).to eq(["/mnt/c"]) + end + + context "when no DrvFs mounts exist" do + let(:mount_output){ "" } + + it "should locate no paths" do + expect(subject.wsl_drvfs_mounts).to eq([]) + end + end + end + + describe ".wsl_drvfs_path?" do + before do + expect(subject).to receive(:wsl_drvfs_mounts).and_return(["/mnt/c"]) + end + + it "should return true when path prefix is found" do + expect(subject.wsl_drvfs_path?("/mnt/c/some/path")).to be_truthy + end + + it "should return false when path prefix is not found" do + expect(subject.wsl_drvfs_path?("/home/vagrant/some/path")).to be_falsey + end + end end end