Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions lib/vagrant-parallels/cap/mount_options.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
require_relative "../util/unix_mount_helpers"

module VagrantPlugins
module Parallels
module SyncedFolderCap
module MountOptions
extend VagrantPlugins::Parallels::Util::UnixMountHelpers

PRL_MOUNT_TYPE = "prl_fs".freeze

# Returns mount options for a parallels synced folder
#
# @param [Machine] machine
# @param [String] name of mount
# @param [String] path of mount on guest
# @param [Hash] hash of mount options
def self.mount_options(machine, name, guest_path, options)
mount_options = options.fetch(:mount_options, [])
detected_ids = detect_owner_group_ids(machine, guest_path, mount_options, options)
mount_uid = detected_ids[:uid]
mount_gid = detected_ids[:gid]

mount_options << "uid=#{mount_uid}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious why are uid/gid mount options used but not share?

mount_options << "gid=#{mount_gid}"
mount_options = mount_options.join(',')
return mount_options, mount_uid, mount_gid
end

def self.mount_type(machine)
return PRL_MOUNT_TYPE
end

def self.mount_name(machine, data)
data[:guestpath].gsub(/[*":<>?|\/\\]/,'_').sub(/^_/, '')
end
end
end
end
end
8 changes: 4 additions & 4 deletions lib/vagrant-parallels/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ class JSONParseError < VagrantParallelsError
error_key(:json_parse_error)
end

class LinuxMountFailed < VagrantParallelsError
error_key(:linux_mount_failed)
end

class LinuxPrlFsInvalidOptions < VagrantParallelsError
error_key(:linux_prl_fs_invalid_options)
end
Expand Down Expand Up @@ -59,6 +55,10 @@ class ParallelsInvalidVersion < VagrantParallelsError
error_key(:parallels_invalid_version)
end

class ParallelsMountFailed < VagrantParallelsError
error_key(:parallels_mount_failed)
end

class ParallelsNotDetected < VagrantParallelsError
error_key(:parallels_not_detected)
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
require 'shellwords'

require_relative "../../util/unix_mount_helpers"

module VagrantPlugins
module Parallels
module GuestLinuxCap
class MountParallelsSharedFolder
extend VagrantPlugins::Parallels::Util::UnixMountHelpers

# Mounts Parallels Desktop shared folder on linux guest
#
# @param [Machine] machine
# @param [String] name of mount
# @param [String] path of mount on guest
# @param [Hash] hash of mount options
def self.mount_parallels_shared_folder(machine, name, guestpath, options)
# Sanity check for mount options: we are not supporting
# VirtualBox-specific 'fmode' and 'dmode' options
Expand All @@ -15,83 +27,38 @@ def self.mount_parallels_shared_folder(machine, name, guestpath, options)
end
end

expanded_guest_path = machine.guest.capability(
:shell_expand_guest_path, guestpath)
guest_path = Shellwords.escape(guestpath)
mount_type = options[:plugin].capability(:mount_type)

mount_commands = []
@@logger.debug("Mounting #{name} (#{options[:hostpath]} to #{guestpath})")

if options[:owner].is_a? Integer
mount_uid = options[:owner]
else
mount_uid = "`id -u #{options[:owner]}`"
end

if options[:group].is_a? Integer
mount_gid = options[:group]
mount_gid_old = options[:group]
else
mount_gid = "`getent group #{options[:group]} | cut -d: -f3`"
mount_gid_old = "`id -g #{options[:group]}`"
end

# First mount command uses getent to get the group
mount_options = "-o uid=#{mount_uid},gid=#{mount_gid}"
mount_options += ",#{options[:mount_options].join(',')}" if options[:mount_options]
mount_commands << "mount -t prl_fs #{mount_options} #{name} #{expanded_guest_path}"

# Second mount command uses the old style `id -g`
mount_options = "-o uid=#{mount_uid},gid=#{mount_gid_old}"
mount_options += ",#{options[:mount_options].join(',')}" if options[:mount_options]
mount_commands << "mount -t prl_fs #{mount_options} #{name} #{expanded_guest_path}"

# Clear prior symlink if exists
if machine.communicate.test("test -L #{expanded_guest_path}")
machine.communicate.sudo("rm #{expanded_guest_path}")
end
mount_options, mount_uid, mount_gid = options[:plugin].capability(:mount_options, name, guest_path, options)
mount_command = "mount -t #{mount_type} -o #{mount_options} #{name} #{guest_path}"

# Create the guest path if it doesn't exist
machine.communicate.sudo("mkdir -p #{expanded_guest_path}")
machine.communicate.sudo("mkdir -p #{guest_path}")

# Attempt to mount the folder. We retry here a few times because
# it can fail early on.
attempts = 0
while true
success = true

mount_commands.each do |command|
no_such_device = false
status = machine.communicate.sudo(command, error_check: false) do |type, data|
no_such_device = true if type == :stderr && data =~ /No such device/i
end

success = status == 0 && !no_such_device
break if success
end

break if success

attempts += 1
if attempts > 10
raise VagrantPlugins::Parallels::Errors::LinuxMountFailed,
command: mount_commands.join("\n")
end

sleep 2
stderr = ""
retryable(on: Errors::ParallelsMountFailed, tries: 3, sleep: 5) do
machine.communicate.sudo(mount_command,
error_class: Errors::ParallelsMountFailed,
error_key: :parallels_mount_failed,
command: mount_command,
output: stderr,
) { |type, data| stderr = data if type == :stderr }
end

# Emit an upstart event if we can
machine.communicate.sudo <<-EOH.gsub(/^ {10}/, "")
if command -v /sbin/init && /sbin/init 2>/dev/null --version | grep upstart; then
/sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{expanded_guest_path}
fi
EOH
emit_upstart_notification(machine, guest_path)
end

def self.unmount_parallels_shared_folder(machine, guestpath, options)
result = machine.communicate.sudo(
"umount #{guestpath}", error_check: false)
guest_path = Shellwords.escape(guestpath)

result = machine.communicate.sudo("umount #{guest_path}", error_check: false)
if result == 0
machine.communicate.sudo("rmdir #{guestpath}", error_check: false)
machine.communicate.sudo("rmdir #{guest_path}", error_check: false)
end
end

Expand Down
15 changes: 15 additions & 0 deletions lib/vagrant-parallels/plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,21 @@ class Plugin < Vagrant.plugin('2')
SyncedFolder
end

synced_folder_capability(:parallels, "mount_name") do
require_relative "cap/mount_options"
SyncedFolderCap::MountOptions
end

synced_folder_capability(:parallels, "mount_options") do
require_relative "cap/mount_options"
SyncedFolderCap::MountOptions
end

synced_folder_capability(:parallels, "mount_type") do
require_relative "cap/mount_options"
SyncedFolderCap::MountOptions
end

# This initializes the internationalization strings.
def self.setup_i18n
I18n.load_path << File.expand_path('locales/en.yml', Parallels.source_root)
Expand Down
28 changes: 9 additions & 19 deletions lib/vagrant-parallels/synced_folder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,11 @@ def enable(machine, folders, _opts)
end

defs << {
name: os_friendly_id(id),
name: data[:plugin].capability(:mount_name, data),
hostpath: hostpath.to_s,
}
end

# We should prepare only folders with unique hostpath values.
# Anyway, duplicates will be mounted later.
defs.uniq! { |d| d[:hostpath] }
driver(machine).share_folders(defs)

# short guestpaths first, so we don't step on ourselves
Expand All @@ -39,8 +36,6 @@ def enable(machine, folders, _opts)
end
end

shf_config = driver(machine).read_shared_folders

# Parallels Shared Folder services can override Vagrant synced folder
# configuration. These services should be pre-configured.
if machine.guest.capability?(:prepare_psf_services)
Expand All @@ -49,12 +44,8 @@ def enable(machine, folders, _opts)

# Go through each folder and mount
machine.ui.output(I18n.t('vagrant.actions.vm.share_folders.mounting'))
folders.each do |_ , data|
# Parallels specific: get id from the VM setting
# It allows to mount one host folder more then one time [GH-105]
id = shf_config.key(data[:hostpath])

if data[:guestpath] and id
folders.each do |id , data|
if data[:guestpath]
# Guest path specified, so mount the folder to specified point
machine.ui.detail(I18n.t('vagrant.actions.vm.share_folders.mounting_entry',
guestpath: data[:guestpath],
Expand All @@ -70,7 +61,11 @@ def enable(machine, folders, _opts)

# Mount the actual folder
machine.guest.capability(
:mount_parallels_shared_folder, id, data[:guestpath], data)
:mount_parallels_shared_folder,
data[:plugin].capability(:mount_name, data),
data[:guestpath],
data
)
else
# If no guest path is specified, then automounting is disabled
machine.ui.detail(I18n.t('vagrant.actions.vm.share_folders.nomount_entry',
Expand All @@ -89,7 +84,7 @@ def disable(machine, folders, _opts)
end

# Remove the shared folders from the VM metadata
names = folders.map { |id, _data| os_friendly_id(id) }
names = folders.map { |_id, data| data[:plugin].capability(:mount_name, data) }
driver(machine).unshare_folders(names)
end

Expand All @@ -103,11 +98,6 @@ def cleanup(machine, opts)
def driver(machine)
machine.provider.driver
end

def os_friendly_id(id)
# Replace chars *, ", :, <, >, ?, |, /, \
id.gsub(/[*":<>?|\/\\]/,'_').sub(/^_/, '')
end
end
end
end
Loading