136 lines
5.0 KiB
Ruby
136 lines
5.0 KiB
Ruby
# Copyright (c) HashiCorp, Inc.
|
|
# SPDX-License-Identifier: BUSL-1.1
|
|
|
|
require "securerandom"
|
|
|
|
module VagrantPlugins
|
|
module GuestDarwin
|
|
module Cap
|
|
class MountVmwareSharedFolder
|
|
|
|
MACOS_BIGSUR_DARWIN_VERSION = 20.freeze
|
|
|
|
# Entry point for hook to called delayed actions
|
|
# which finalizing the synced folders setup on
|
|
# the guest
|
|
def self.write_apfs_firmlinks(env)
|
|
if env && env[:machine] && delayed = apfs_firmlinks_delayed.delete(env[:machine].id)
|
|
delayed.call
|
|
end
|
|
end
|
|
|
|
# @return [Hash] storage location for delayed actions
|
|
def self.apfs_firmlinks_delayed
|
|
if !@_apfs_firmlinks
|
|
@_apfs_firmlinks = {}
|
|
end
|
|
@_apfs_firmlinks
|
|
end
|
|
|
|
# we seem to be unable to ask 'mount -t vmhgfs' to mount the roots
|
|
# of specific shares, so instead we symlink from what is already
|
|
# mounted by the guest tools
|
|
# (ie. the behaviour of the VMware_fusion provider prior to 0.8.x)
|
|
|
|
def self.mount_vmware_shared_folder(machine, name, guestpath, options)
|
|
# Use this variable to determine which machines
|
|
# have been registered with after hook
|
|
@apply_firmlinks ||= Hash.new{ |h, k| h[k] = {bootstrap: false, content: []} }
|
|
|
|
machine.communicate.tap do |comm|
|
|
# check if we are dealing with an APFS root container
|
|
if comm.test("test -d /System/Volumes/Data")
|
|
parts = Pathname.new(guestpath).descend.to_a
|
|
firmlink = parts[1].to_s
|
|
firmlink.slice!(0, 1) if firmlink.start_with?("/")
|
|
if parts.size > 2
|
|
guestpath = File.join("/System/Volumes/Data", guestpath)
|
|
else
|
|
guestpath = nil
|
|
end
|
|
end
|
|
|
|
# Remove existing symlink or directory if defined
|
|
if guestpath
|
|
if comm.test("test -L \"#{guestpath}\"")
|
|
comm.sudo("rm -f \"#{guestpath}\"")
|
|
elsif comm.test("test -d \"#{guestpath}\"")
|
|
comm.sudo("rm -Rf \"#{guestpath}\"")
|
|
end
|
|
|
|
# create intermediate directories if needed
|
|
intermediate_dir = File.dirname(guestpath)
|
|
if intermediate_dir != "/"
|
|
comm.sudo("mkdir -p \"#{intermediate_dir}\"")
|
|
end
|
|
|
|
comm.sudo("ln -s \"/Volumes/VMware Shared Folders/#{name}\" \"#{guestpath}\"")
|
|
end
|
|
|
|
if firmlink && !system_firmlink?(firmlink)
|
|
if guestpath.nil?
|
|
guestpath = "/Volumes/VMware Shared Folders/#{name}"
|
|
else
|
|
guestpath = File.join("/System/Volumes/Data", firmlink)
|
|
end
|
|
|
|
share_line = "#{firmlink}\t#{guestpath}"
|
|
|
|
# Check if the line is already defined. If so, bail since we are done
|
|
if !comm.test("[[ \"$(</etc/synthetic.conf)\" = *\"#{share_line}\"* ]]")
|
|
@apply_firmlinks[machine.id][:bootstrap] = true
|
|
end
|
|
|
|
# If we haven't already added our hook to apply firmlinks, do it now
|
|
if @apply_firmlinks[machine.id][:content].empty?
|
|
apfs_firmlinks_delayed[machine.id] = proc do
|
|
content = @apply_firmlinks[machine.id][:content].join("\n")
|
|
# Write out the synthetic file
|
|
comm.sudo("echo -e #{content.inspect} > /etc/synthetic.conf")
|
|
if @apply_firmlinks[machine.id][:bootstrap]
|
|
if machine.guest.capability("darwin_major_version").to_i < MACOS_BIGSUR_DARWIN_VERSION
|
|
apfs_bootstrap_flag = "-B"
|
|
expected_rc = 0
|
|
else
|
|
apfs_bootstrap_flag = "-t"
|
|
expected_rc = 253
|
|
end
|
|
# Re-bootstrap the root container to pick up firmlink updates
|
|
comm.sudo("/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util #{apfs_bootstrap_flag}", good_exit: [expected_rc])
|
|
end
|
|
end
|
|
end
|
|
@apply_firmlinks[machine.id][:content] << share_line
|
|
end
|
|
end
|
|
end
|
|
|
|
# Check if firmlink is provided by the system
|
|
#
|
|
# @param [String] firmlink Firmlink path
|
|
# @return [Boolean]
|
|
def self.system_firmlink?(firmlink)
|
|
if !@_firmlinks
|
|
if File.exist?("/usr/share/firmlinks")
|
|
@_firmlinks = File.readlines("/usr/share/firmlinks").map do |line|
|
|
line.split.first
|
|
end
|
|
else
|
|
@_firmlinks = []
|
|
end
|
|
end
|
|
firmlink = "/#{firmlink}" if !firmlink.start_with?("/")
|
|
@_firmlinks.include?(firmlink)
|
|
end
|
|
|
|
# @private
|
|
# Reset the cached values for capability. This is not considered a public
|
|
# API and should only be used for testing.
|
|
def self.reset!
|
|
instance_variables.each(&method(:remove_instance_variable))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|