diff --git a/plugins/hosts/linux/cap/nfs.rb b/plugins/hosts/linux/cap/nfs.rb index 74f723f15..862ee34b3 100644 --- a/plugins/hosts/linux/cap/nfs.rb +++ b/plugins/hosts/linux/cap/nfs.rb @@ -37,15 +37,7 @@ module VagrantPlugins sleep 0.5 nfs_cleanup(id) - - # Only use "sudo" if we can't write to /etc/exports directly - sudo_command = "" - sudo_command = "sudo " if !File.writable?("/etc/exports") - - output.split("\n").each do |line| - line = Vagrant::Util::ShellQuote.escape(line, "'") - system(%Q[echo '#{line}' | #{sudo_command}tee -a /etc/exports >/dev/null]) - end + nfs_write_exports(output) if nfs_running?(nfs_check_command) system("sudo #{nfs_apply_command}") @@ -67,50 +59,70 @@ module VagrantPlugins logger = Log4r::Logger.new("vagrant::hosts::linux") logger.info("Pruning invalid NFS entries...") - output = false user = Process.uid - File.read("/etc/exports").lines.each do |line| - if id = line[/^# VAGRANT-BEGIN:( #{user})? ([\.\/A-Za-z0-9\-_:]+?)$/, 2] - if valid_ids.include?(id) - logger.debug("Valid ID: #{id}") - else - if !output - # We want to warn the user but we only want to output once - ui.info I18n.t("vagrant.hosts.linux.nfs_prune") - output = true - end + # Create editor instance for removing invalid IDs + editor = Util::StringBlockEditor.new(File.read("/etc/exports")) - logger.info("Invalid ID, pruning: #{id}") - nfs_cleanup(id) - end - end + # Build composite IDs with UID information and discover invalid entries + composite_ids = valid_ids.map do |v_id| + "#{user} #{v_id}" + end + remove_ids = editor.keys - composite_keys + + logger.debug("Known valid NFS export IDs: #{valid_ids}") + logger.debug("Composite valid NFS export IDs with user: #{composite_ids}") + logger.debug("NFS export IDs to be removed: #{remove_ids}") + if !remove_ids.empty? + ui.info I18n.t("vagrant.hosts.linux.nfs_prune") + nfs_cleanup(remove_ids) end end protected - def self.nfs_cleanup(id) + def self.nfs_cleanup(remove_ids) return if !File.exist?("/etc/exports") - # Only use "sudo" if we can't write to /etc/exports directly - sudo_command = "" - sudo_command = "sudo " if !File.writable?("/etc/exports") + editor = Util::StringBlockEditor.new(File.read("/etc/exports")) + remove_ids = Array(remove_ids) - # Strip out the block of code which was inserted by Vagrant - user = Regexp.escape(Process.uid.to_s) - id = Regexp.escape(id.to_s) - exports_in = File.read('/etc/exports') - exports_out = exports_in.gsub(%r{ - ^\#\ VAGRANT-BEGIN:((?:\ #{user})?\ #{id})$ - .*? - ^\#\ VAGRANT-END:\1$ - \n? - }mx, '') - if exports_out != exports_in - open(%Q[|#{sudo_command}tee /etc/exports >/dev/null], 'w+') do |p| - p.write(exports_out) + # Remove all invalid ID entries + remove_ids.each do |r_id| + editor.delete(r_id) + end + nfs_write_exports(editor.value) + end + + def self.nfs_write_exports(new_exports_content) + # Write contents out to temporary file + new_exports_file = Tempfile.create('vagrant') + new_exports_file.puts(new_exports_content) + new_exports_file.close + new_exports_path = new_exports_file.path + + if !FileUtils.compare_file(new_exports_path, "/etc/exports") + # Only use "sudo" if we can't write to /etc/exports directly + sudo_command = "" + sudo_command = "sudo " if !File.writable?("/etc/exports") + + # Ensure new file mode and uid/gid match existing file to replace + existing_stat = File.stat("/etc/exports") + new_stat = File.stat(new_exports_path) + if existing_stat.mode != new_stat.mode + File.chmod(existing_stat.mode, new_exports_path) end + # TODO: Error check + if existing_stat.uid != new_stat.uid || existing_stat.gid != new_stat.gid + system("#{sudo_command}chown #{existing_stat.uid}:#{existing_stat.gid} #{new_exports_path}") + end + + # Replace existing exports file + system("#{sudo_command}mv #{new_exports_path} /etc/exports") + end + ensure + if File.exist?(new_exports_path) + File.unlink(new_exports_path) end end