Using the resolve_host_path capability allows hosts to modify the shared folder directory while keeping the logic contained within the specific host implementation.
199 lines
6.4 KiB
Ruby
199 lines
6.4 KiB
Ruby
require "log4r"
|
|
|
|
require "vagrant/util"
|
|
require "vagrant/util/shell_quote"
|
|
require "vagrant/util/which"
|
|
|
|
module VagrantPlugins
|
|
module HostBSD
|
|
module Cap
|
|
class NFS
|
|
def self.nfs_export(environment, ui, id, ips, folders)
|
|
nfs_exports_template = environment.host.capability(:nfs_exports_template)
|
|
nfs_restart_command = environment.host.capability(:nfs_restart_command)
|
|
logger = Log4r::Logger.new("vagrant::hosts::bsd")
|
|
|
|
nfs_checkexports! if File.file?("/etc/exports")
|
|
|
|
# We need to build up mapping of directories that are enclosed
|
|
# within each other because the exports file has to have subdirectories
|
|
# of an exported directory on the same line. e.g.:
|
|
#
|
|
# "/foo" "/foo/bar" ...
|
|
# "/bar"
|
|
#
|
|
# We build up this mapping within the following hash.
|
|
logger.debug("Compiling map of sub-directories for NFS exports...")
|
|
dirmap = {}
|
|
folders.sort_by { |_, opts| opts[:hostpath] }.each do |_, opts|
|
|
opts[:hostpath] = environment.host.capability(:resolve_host_path, opts[:hostpath].gsub('"', '\"'))
|
|
hostpath = opts[:hostpath].dup
|
|
|
|
found = false
|
|
dirmap.each do |dirs, diropts|
|
|
dirs.each do |dir|
|
|
if dir.start_with?(hostpath) || hostpath.start_with?(dir)
|
|
# TODO: verify opts and diropts are _identical_, raise an error
|
|
# if not. NFS mandates subdirectories have identical options.
|
|
dirs << hostpath
|
|
found = true
|
|
break
|
|
end
|
|
end
|
|
|
|
break if found
|
|
end
|
|
|
|
if !found
|
|
dirmap[[hostpath]] = opts.dup
|
|
end
|
|
end
|
|
|
|
# Sort all the keys by length so that the directory closest to
|
|
# the root is exported first. Also, remove duplicates so that
|
|
# checkexports will work properly.
|
|
dirmap.each do |dirs, _|
|
|
dirs.uniq!
|
|
dirs.sort_by! { |d| d.length }
|
|
end
|
|
|
|
# Setup the NFS options
|
|
dirmap.each do |dirs, opts|
|
|
if !opts[:bsd__nfs_options]
|
|
opts[:bsd__nfs_options] = ["alldirs"]
|
|
end
|
|
|
|
hasmapall = false
|
|
opts[:bsd__nfs_options].each do |opt|
|
|
# mapall/maproot are mutually exclusive, so we have to check
|
|
# for both here.
|
|
if opt =~ /^mapall=/ || opt =~ /^maproot=/
|
|
hasmapall = true
|
|
break
|
|
end
|
|
end
|
|
|
|
if !hasmapall
|
|
opts[:bsd__nfs_options] << "mapall=#{opts[:map_uid]}:#{opts[:map_gid]}"
|
|
end
|
|
|
|
opts[:bsd__compiled_nfs_options] = opts[:bsd__nfs_options].map do |opt|
|
|
"-#{opt}"
|
|
end.join(" ")
|
|
end
|
|
|
|
logger.info("Exporting the following for NFS...")
|
|
dirmap.each do |dirs, opts|
|
|
logger.info("NFS DIR: #{dirs.inspect}")
|
|
logger.info("NFS OPTS: #{opts.inspect}")
|
|
end
|
|
|
|
output = Vagrant::Util::TemplateRenderer.render(nfs_exports_template,
|
|
uuid: id,
|
|
ips: ips,
|
|
folders: dirmap,
|
|
user: Process.uid)
|
|
|
|
# The sleep ensures that the output is truly flushed before any `sudo`
|
|
# commands are issued.
|
|
ui.info I18n.t("vagrant.hosts.bsd.nfs_export")
|
|
sleep 0.5
|
|
|
|
# First, clean up the old entry
|
|
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 the rendered template into the exports
|
|
output.split("\n").each do |line|
|
|
line = Vagrant::Util::ShellQuote.escape(line, "'")
|
|
system(
|
|
"echo '#{line}' | " +
|
|
"#{sudo_command}/usr/bin/tee -a /etc/exports >/dev/null")
|
|
end
|
|
|
|
# We run restart here instead of "update" just in case nfsd
|
|
# is not starting
|
|
system(*nfs_restart_command)
|
|
end
|
|
|
|
def self.nfs_exports_template(environment)
|
|
"nfs/exports_bsd"
|
|
end
|
|
|
|
def self.nfs_installed(environment)
|
|
!!Vagrant::Util::Which.which("nfsd")
|
|
end
|
|
|
|
def self.nfs_prune(environment, ui, valid_ids)
|
|
return if !File.exist?("/etc/exports")
|
|
|
|
logger = Log4r::Logger.new("vagrant::hosts::bsd")
|
|
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.bsd.nfs_prune")
|
|
output = true
|
|
end
|
|
|
|
logger.info("Invalid ID, pruning: #{id}")
|
|
nfs_cleanup(id)
|
|
end
|
|
end
|
|
end
|
|
rescue Errno::EACCES
|
|
raise Vagrant::Errors::NFSCantReadExports
|
|
end
|
|
|
|
def self.nfs_restart_command(environment)
|
|
["sudo", "nfsd", "restart"]
|
|
end
|
|
|
|
protected
|
|
|
|
def self.nfs_cleanup(id)
|
|
return if !File.exist?("/etc/exports")
|
|
|
|
# Escape sed-sensitive characters:
|
|
id = id.gsub("/", "\\/")
|
|
id = id.gsub(".", "\\.")
|
|
|
|
user = Process.uid
|
|
|
|
command = []
|
|
command << "sudo" if !File.writable?("/etc/exports")
|
|
command += [
|
|
"sed", "-E", "-e",
|
|
"/^# VAGRANT-BEGIN:( #{user})? #{id}/," +
|
|
"/^# VAGRANT-END:( #{user})? #{id}/ d",
|
|
"-ibak",
|
|
"/etc/exports"
|
|
]
|
|
|
|
# Use sed to just strip out the block of code which was inserted
|
|
# by Vagrant, and restart NFS.
|
|
system(*command)
|
|
end
|
|
|
|
def self.nfs_checkexports!
|
|
r = Vagrant::Util::Subprocess.execute("nfsd", "checkexports")
|
|
if r.exit_code != 0
|
|
raise Vagrant::Errors::NFSBadExports, output: r.stderr
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|