diff --git a/plugins/guests/linux/cap/mount_nfs.rb b/plugins/guests/linux/cap/mount_nfs.rb index 27be70a33..e220fd379 100644 --- a/plugins/guests/linux/cap/mount_nfs.rb +++ b/plugins/guests/linux/cap/mount_nfs.rb @@ -9,39 +9,42 @@ module VagrantPlugins def self.mount_nfs_folder(machine, ip, folders) comm = machine.communicate - commands = [] - folders.each do |name, opts| - # Expand the guest path so we can handle things like "~/vagrant" - expanded_guest_path = machine.guest.capability( - :shell_expand_guest_path, opts[:guestpath]) + # Mount each folder separately so we can retry. + commands = ["set -e"] - # Do the actual creating and mounting - commands << "mkdir -p '#{expanded_guest_path}'" + # Shellescape the paths in case they do not have special characters. + guest_path = Shellwords.escape(opts[:guestpath]) + host_path = Shellwords.escape(opts[:hostpath]) - # Mount - hostpath = opts[:hostpath].dup - hostpath.gsub!("'", "'\\\\''") - - # Figure out any options - mount_opts = ["vers=#{opts[:nfs_version]}"] + # Build the list of mount options. + mount_opts = [] + mount_opts << "vers=#{opts[:nfs_version]}" if opts[:nfs_version] mount_opts << "udp" if opts[:nfs_udp] if opts[:mount_options] - mount_opts = opts[:mount_options].dup + mount_opts = mount_opts + opts[:mount_options].dup end + mount_opts = mount_opts.join(",") - commands << "mount -o #{mount_opts.join(",")} '#{ip}:#{hostpath}' '#{expanded_guest_path}'" + # Make the directory on the guest. + commands << "mkdir -p #{guest_path}" + + # Perform the mount operation. + commands << "mount -o #{mount_opts} #{ip}:#{host_path} #{guest_path}" # Emit a mount event commands << <<-EOH.gsub(/^ {14}/, '') if command -v /sbin/init && /sbin/init --version | grep upstart; then - /sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT='#{expanded_guest_path}' + /sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{guest_path} fi EOH - end - retryable(on: Vagrant::Errors::NFSMountFailed, tries: 8, sleep: 3) do - comm.sudo(commands.join("\n"), error_class: Vagrant::Errors::NFSMountFailed) + # Run the command, raising a specific error. + retryable(on: Vagrant::Errors::NFSMountFailed, tries: 3, sleep: 5) do + machine.communicate.sudo(commands.join("\n"), + error_class: Vagrant::Errors::NFSMountFailed, + ) + end end end end diff --git a/test/unit/plugins/guests/linux/cap/mount_nfs_test.rb b/test/unit/plugins/guests/linux/cap/mount_nfs_test.rb index 6a53c33ed..5fcd55de4 100644 --- a/test/unit/plugins/guests/linux/cap/mount_nfs_test.rb +++ b/test/unit/plugins/guests/linux/cap/mount_nfs_test.rb @@ -42,8 +42,8 @@ describe "VagrantPlugins::GuestLinux::Cap::MountNFS" do } cap.mount_nfs_folder(machine, ip, folders) - expect(comm.received_commands[0]).to match(/mkdir -p '#{guestpath}'/) - expect(comm.received_commands[0]).to match(/'1.2.3.4:#{hostpath}' '#{guestpath}'/) + expect(comm.received_commands[0]).to match(/mkdir -p #{guestpath}/) + expect(comm.received_commands[0]).to match(/1.2.3.4:#{hostpath} #{guestpath}/) end it "mounts with options" do @@ -72,7 +72,20 @@ describe "VagrantPlugins::GuestLinux::Cap::MountNFS" do cap.mount_nfs_folder(machine, ip, folders) expect(comm.received_commands[0]).to include( - "/sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT='#{guestpath}'") + "/sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{guestpath}") + end + + it "escapes host and guest paths" do + folders = { + "/vagrant-nfs" => { + guestpath: "/guest with spaces", + hostpath: "/host's", + } + } + cap.mount_nfs_folder(machine, ip, folders) + + expect(comm.received_commands[0]).to match(/host\\\'s/) + expect(comm.received_commands[0]).to match(/guest\\\ with\\\ spaces/) end end end