diff --git a/lib/vagrant-sshfs/cap/host/linux/sshfs_reverse_mount.rb b/lib/vagrant-sshfs/cap/host/linux/sshfs_reverse_mount.rb new file mode 100644 index 0000000..2aa5b23 --- /dev/null +++ b/lib/vagrant-sshfs/cap/host/linux/sshfs_reverse_mount.rb @@ -0,0 +1,176 @@ +require "log4r" +require "vagrant/util/retryable" +require "tempfile" + +# This is already done for us in lib/vagrant-sshfs.rb. We needed to +# do it there before Process.uid is called the first time by Vagrant +# This provides a new Process.create() that works on Windows. +if Vagrant::Util::Platform.windows? + require 'win32/process' +end + +module VagrantPlugins + module HostLinux + module Cap + class MountSSHFS + extend Vagrant::Util::Retryable + @@logger = Log4r::Logger.new("vagrant::synced_folders::sshfs_reverse_mount") + + def self.sshfs_reverse_is_folder_mounted(env, opts) + mounted = false + hostpath = opts[:hostpath].dup + hostpath.gsub!("'", "'\\\\''") + hostpath = hostpath.chomp('/') # remove trailing / if exists + cat_cmd = Vagrant::Util::Which.which('cat') + result = Vagrant::Util::Subprocess.execute(cat_cmd, '/proc/mounts') + mounts = File.open('/proc/mounts', 'r') + mounts.each_line do |line| + if line.split()[1] == hostpath + mounted = true + break + end + end + return mounted + end + + def self.sshfs_reverse_mount_folder(env, machine, opts) + # opts contains something like: + # { :type=>:sshfs, + # :guestpath=>"/sharedfolder", + # :hostpath=>"/guests/sharedfolder", + # :disabled=>false + # :ssh_host=>"192.168.1.1" + # :ssh_port=>"22" + # :ssh_username=>"username" + # :ssh_password=>"password" + # } + self.sshfs_mount(machine, opts) + end + + def self.sshfs_reverse_unmount_folder(env, machine, opts) + self.sshfs_unmount(machine, opts) + end + + protected + + # Perform a mount by running an sftp-server on the vagrant host + # and piping stdin/stdout to sshfs running inside the guest + def self.sshfs_mount(machine, opts) + + sshfs_path = Vagrant::Util::Which.which('sshfs') + + # expand the guest path so we can handle things like "~/vagrant" + expanded_guest_path = machine.guest.capability( + :shell_expand_guest_path, opts[:guestpath]) + + # Mount path information + hostpath = opts[:hostpath].dup + hostpath.gsub!("'", "'\\\\''") + + # Add in some sshfs/fuse options that are common to both mount methods + opts[:sshfs_opts] = ' -o noauto_cache '# disable caching based on mtime + + # Add in some ssh options that are common to both mount methods + opts[:ssh_opts] = ' -o StrictHostKeyChecking=no '# prevent yes/no question + opts[:ssh_opts]+= ' -o ServerAliveInterval=30 ' # send keepalives + + # SSH connection options + ssh_opts = opts[:ssh_opts] + ssh_opts+= ' -o Port=' + machine.ssh_info[:port].to_s + ssh_opts+= ' -o IdentityFile=' + machine.ssh_info[:private_key_path][0] + ssh_opts+= ' -o UserKnownHostsFile=/dev/null ' + ssh_opts+= ' -F /dev/null ' # Don't pick up options from user's config + + ssh_opts_append = opts[:ssh_opts_append].to_s # provided by user + + # SSHFS executable options + sshfs_opts = opts[:sshfs_opts] + sshfs_opts_append = opts[:sshfs_opts_append].to_s # provided by user + + username = machine.ssh_info[:username] + host = machine.ssh_info[:host] + + + # The sshfs command to mount the guest directory on the host + sshfs_cmd = "#{sshfs_path} #{ssh_opts} #{ssh_opts_append} " + sshfs_cmd+= "#{sshfs_opts} #{sshfs_opts_append} " + sshfs_cmd+= "#{username}@#{host}:#{expanded_guest_path} #{hostpath}" + + # Log some information + @@logger.debug("sshfs cmd: #{sshfs_cmd}") + + machine.ui.info(I18n.t("vagrant.sshfs.actions.reverse_mounting_folder", + hostpath: hostpath, guestpath: expanded_guest_path)) + + # Log STDERR to predictable files so that we can inspect them + # later in case things go wrong. We'll use the machines data + # directory (i.e. .vagrant/machines/default/virtualbox/) for this + f1path = machine.data_dir.join('vagrant_sshfs_sshfs_stderr.txt') + f1 = File.new(f1path, 'w+') + + # Launch sshfs command to mount guest dir into the host + if Vagrant::Util::Platform.windows? + # Need to handle Windows differently. Kernel.spawn fails to work, + # if the shell creating the process is closed. + # See https://github.com/dustymabe/vagrant-sshfs/issues/31 + Process.create(:command_line => ssh_cmd, + :creation_flags => Process::DETACHED_PROCESS, + :process_inherit => false, + :thread_inherit => true, + :startup_info => {:stdin => w2, :stdout => r1, :stderr => f1}) + else + p1 = spawn(sshfs_cmd, :out => f1, :err => f1, :pgroup => true) + Process.detach(p1) # Detach so process will keep running + end + + # Check that the mount made it + mounted = false + for i in 0..6 + machine.ui.info("Checking Mount..") + if self.sshfs_reverse_is_folder_mounted(machine, opts) + mounted = true + break + end + sleep(2) + end + if !mounted + f1.rewind # Seek to beginning of the file + error_class = VagrantPlugins::SyncedFolderSSHFS::Errors::SSHFSReverseMountFailed + raise error_class, sshfs_output: f1.read + end + machine.ui.info("Folder Successfully Mounted!") + end + + def self.sshfs_unmount(machine, opts) + # opts contains something like: + # { :type=>:sshfs, + # :guestpath=>"/sharedfolder", + # :hostpath=>"/guests/sharedfolder", + # :disabled=>false + # :ssh_host=>"192.168.1.1" + # :ssh_port=>"22" + # :ssh_username=>"username" + # :ssh_password=>"password" + # } + + # Mount path information + hostpath = opts[:hostpath].dup + hostpath.gsub!("'", "'\\\\''") + + # Log some information + machine.ui.info(I18n.t("vagrant.sshfs.actions.reverse_unmounting_folder", + hostpath: hostpath)) + + # Build up the command and connect + error_class = VagrantPlugins::SyncedFolderSSHFS::Errors::SSHFSUnmountFailed + fusermount_cmd = Vagrant::Util::Which.which('fusermount') + cmd = "#{fusermount_cmd} -u #{hostpath}" + result = Vagrant::Util::Subprocess.execute(*cmd.split()) + if result.exit_code != 0 + raise error_class, command: cmd, stdout: result.stdout, stderr: result.stderr + end + end + end + end + end +end diff --git a/lib/vagrant-sshfs/errors.rb b/lib/vagrant-sshfs/errors.rb index d28d303..e9c7964 100644 --- a/lib/vagrant-sshfs/errors.rb +++ b/lib/vagrant-sshfs/errors.rb @@ -14,6 +14,10 @@ class SSHFSSlaveMountFailed < SSHFSError error_key(:slave_mount_failed) end + class SSHFSReverseMountFailed < SSHFSError + error_key(:reverse_mount_failed) + end + class SSHFSUnmountFailed < SSHFSError error_key(:unmount_failed) end diff --git a/lib/vagrant-sshfs/plugin.rb b/lib/vagrant-sshfs/plugin.rb index 5b379e8..0e98b5b 100644 --- a/lib/vagrant-sshfs/plugin.rb +++ b/lib/vagrant-sshfs/plugin.rb @@ -20,6 +20,21 @@ class Plugin < Vagrant.plugin("2") Command::SSHFS end + host_capability("linux", "sshfs_reverse_mount_folder") do + require_relative "cap/host/linux/sshfs_reverse_mount" + VagrantPlugins::HostLinux::Cap::MountSSHFS + end + + host_capability("linux", "sshfs_reverse_unmount_folder") do + require_relative "cap/host/linux/sshfs_reverse_mount" + VagrantPlugins::HostLinux::Cap::MountSSHFS + end + + host_capability("linux", "sshfs_reverse_is_folder_mounted") do + require_relative "cap/host/linux/sshfs_reverse_mount" + VagrantPlugins::HostLinux::Cap::MountSSHFS + end + guest_capability("linux", "sshfs_forward_mount_folder") do require_relative "cap/guest/linux/sshfs_forward_mount" VagrantPlugins::GuestLinux::Cap::MountSSHFS diff --git a/lib/vagrant-sshfs/synced_folder.rb b/lib/vagrant-sshfs/synced_folder.rb index 503cbce..b12f5cd 100644 --- a/lib/vagrant-sshfs/synced_folder.rb +++ b/lib/vagrant-sshfs/synced_folder.rb @@ -4,6 +4,7 @@ require "vagrant/util/which" require_relative "synced_folder/sshfs_forward_mount" +require_relative "synced_folder/sshfs_reverse_mount" module VagrantPlugins module SyncedFolderSSHFS @@ -39,7 +40,12 @@ def enable(machine, folders, pluginopts) # Iterate through the folders and mount if needed folders.each do |id, opts| - do_forward_mount(machine, opts) + + if opts.has_key?(:reverse) and opts[:reverse] + do_reverse_mount(machine, opts) + else + do_forward_mount(machine, opts) + end end end @@ -57,7 +63,11 @@ def disable(machine, folders, opts) # Iterate through the folders and mount if needed folders.each do |id, opts| - do_forward_unmount(machine, opts) + if opts.has_key?(:reverse) and opts[:reverse] + do_reverse_unmount(machine, opts) + else + do_forward_unmount(machine, opts) + end end end diff --git a/lib/vagrant-sshfs/synced_folder/sshfs_forward_mount.rb b/lib/vagrant-sshfs/synced_folder/sshfs_forward_mount.rb index 8e83d6e..a495878 100644 --- a/lib/vagrant-sshfs/synced_folder/sshfs_forward_mount.rb +++ b/lib/vagrant-sshfs/synced_folder/sshfs_forward_mount.rb @@ -28,7 +28,7 @@ def do_forward_mount(machine, opts) if machine.guest.capability(:sshfs_forward_is_folder_mounted, opts) machine.ui.info( I18n.t("vagrant.sshfs.info.already_mounted", - folder: opts[:guestpath])) + location: 'guest', folder: opts[:guestpath])) return end @@ -58,7 +58,7 @@ def do_forward_unmount(machine, opts) if ! machine.guest.capability(:sshfs_forward_is_folder_mounted, opts) machine.ui.info( I18n.t("vagrant.sshfs.info.not_mounted", - folder: opts[:guestpath])) + location: 'guest', folder: opts[:guestpath])) return end diff --git a/lib/vagrant-sshfs/synced_folder/sshfs_reverse_mount.rb b/lib/vagrant-sshfs/synced_folder/sshfs_reverse_mount.rb new file mode 100644 index 0000000..0f19f9c --- /dev/null +++ b/lib/vagrant-sshfs/synced_folder/sshfs_reverse_mount.rb @@ -0,0 +1,51 @@ +require "log4r" + +require "vagrant/util/platform" +require "vagrant/util/which" + +module VagrantPlugins + module SyncedFolderSSHFS + class SyncedFolder < Vagrant.plugin("2", :synced_folder) + + protected + + # Do a reverse mount: mounting guest folder onto the host + def do_reverse_mount(machine, opts) + + # Check to see if sshfs software is in the host + if machine.env.host.capability?(:sshfs_installed) + if !machine.env.host.capability(:sshfs_installed) + raise VagrantPlugins::SyncedFolderSSHFS::Errors::SSHFSNotInstalledInHost + end + end + + # If already mounted then there is nothing to do + if machine.env.host.capability(:sshfs_reverse_is_folder_mounted, opts) + machine.ui.info( + I18n.t("vagrant.sshfs.info.already_mounted", + location: 'host', folder: opts[:hostpath])) + return + end + + # Do the mount + machine.ui.info(I18n.t("vagrant.sshfs.actions.mounting")) + machine.env.host.capability(:sshfs_reverse_mount_folder, machine, opts) + end + + def do_reverse_unmount(machine, opts) + + # If not mounted then there is nothing to do + if ! machine.env.host.capability(:sshfs_reverse_is_folder_mounted, opts) + machine.ui.info( + I18n.t("vagrant.sshfs.info.not_mounted", + location: 'host', folder: opts[:hostpath])) + return + end + + # Do the Unmount + machine.ui.info(I18n.t("vagrant.sshfs.actions.unmounting")) + machine.env.host.capability(:sshfs_reverse_unmount_folder, machine, opts) + end + end + end +end diff --git a/locales/synced_folder_sshfs.yml b/locales/synced_folder_sshfs.yml index 0963bd7..7d6324b 100644 --- a/locales/synced_folder_sshfs.yml +++ b/locales/synced_folder_sshfs.yml @@ -7,6 +7,10 @@ en: unmounting: Unmounting SSHFS shared folder... unmounting_folder: |- Unmounting SSHFS shared folder mounted at %{guestpath} + reverse_unmounting_folder: |- + Unmounting SSHFS shared folder mounted at host's %{hostpath} + reverse_mounting_folder: |- + mounting folder via SSHFS: guestpath:%{guestpath} => hostpath:%{hostpath} slave_mounting_folder: |- Mounting folder via SSHFS: %{hostpath} => %{guestpath} normal_mounting_folder: |- @@ -18,9 +22,9 @@ en: detected_host_ip: |- Detected host IP address is '%{ip}' already_mounted: |- - The folder %{folder} in the guest already mounted. + The folder %{folder} in the %{location} is already mounted. not_mounted: |- - The folder %{folder} in the guest is not mounted. + The folder %{folder} in the %{location} is not mounted. errors: communicator_not_ready: |- The machine is reporting that it is not ready to communicate via ssh. Verify @@ -55,6 +59,13 @@ en: Stderr from the command: %{stderr} + reverse_mount_failed: |- + Mounting SSHFS shared folder via reverse SSHFS mount failed. Please + look at the below output from from the processes that were run. + + SSHFS command output: + + %{sshfs_output} slave_mount_failed: |- Mounting SSHFS shared folder via slave SSHFS mount failed. Please look at the below STDERR output from the processes that were run.