-
Notifications
You must be signed in to change notification settings - Fork 4.4k
/
nfs.rb
198 lines (164 loc) · 6.43 KB
/
nfs.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
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