Introduce support for handling box architecture. Adds a new `box_architecture` setting that defaults to `:auto` which will perform automatic detection of the host system, but can be overridden with a custom value. Can also be set to `nil` which will result in it fetching the box flagged with the default architecture within the metadata. Box collection has been modified to allow existing boxes already downloaded and unpacked to still function as expected when architecture information is not available.
738 lines
27 KiB
Ruby
738 lines
27 KiB
Ruby
# Copyright (c) HashiCorp, Inc.
|
|
# SPDX-License-Identifier: BUSL-1.1
|
|
|
|
require "rbconfig"
|
|
require "shellwords"
|
|
require "tempfile"
|
|
require "tmpdir"
|
|
require "log4r"
|
|
|
|
require "vagrant/util/subprocess"
|
|
require "vagrant/util/powershell"
|
|
require "vagrant/util/which"
|
|
|
|
module Vagrant
|
|
module Util
|
|
# This class just contains some platform checking code.
|
|
class Platform
|
|
class << self
|
|
|
|
# Detect architecture of host system
|
|
#
|
|
# @return [String]
|
|
def architecture
|
|
if !defined?(@_host_architecture)
|
|
if ENV["VAGRANT_HOST_ARCHITECTURE"].to_s != ""
|
|
return @_host_architecture = ENV["VAGRANT_HOST_ARCHITECTURE"]
|
|
end
|
|
|
|
@_host_architecture = case RbConfig::CONFIG["target_cpu"]
|
|
when "x86_64"
|
|
"amd64"
|
|
when "i386"
|
|
"386"
|
|
when "arm64", "aarch64"
|
|
"arm64"
|
|
else
|
|
RbConfig::CONFIG["target_cpu"]
|
|
end
|
|
end
|
|
@_host_architecture
|
|
end
|
|
|
|
def logger
|
|
if !defined?(@_logger)
|
|
@_logger = Log4r::Logger.new("vagrant::util::platform")
|
|
end
|
|
@_logger
|
|
end
|
|
|
|
def cygwin?
|
|
if !defined?(@_cygwin)
|
|
@_cygwin = ENV["VAGRANT_DETECTED_OS"].to_s.downcase.include?("cygwin") ||
|
|
platform.include?("cygwin") ||
|
|
ENV["OSTYPE"].to_s.downcase.include?("cygwin")
|
|
end
|
|
@_cygwin
|
|
end
|
|
|
|
def msys?
|
|
if !defined?(@_msys)
|
|
@_msys = ENV["VAGRANT_DETECTED_OS"].to_s.downcase.include?("msys") ||
|
|
platform.include?("msys") ||
|
|
ENV["OSTYPE"].to_s.downcase.include?("msys")
|
|
end
|
|
@_msys
|
|
end
|
|
|
|
def wsl?
|
|
if !defined?(@_wsl)
|
|
@_wsl = false
|
|
SilenceWarnings.silence! do
|
|
# Find 'microsoft' in /proc/version indicative of WSL
|
|
if File.file?('/proc/version')
|
|
osversion = File.open('/proc/version', &:gets)
|
|
if osversion.downcase.include?("microsoft")
|
|
@_wsl = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
@_wsl
|
|
end
|
|
|
|
[:darwin, :bsd, :freebsd, :linux, :solaris].each do |type|
|
|
define_method("#{type}?") do
|
|
platform.include?(type.to_s)
|
|
end
|
|
end
|
|
|
|
def windows?
|
|
return @_windows if defined?(@_windows)
|
|
@_windows = %w[mingw mswin].any? { |t| platform.include?(t) }
|
|
return @_windows
|
|
end
|
|
|
|
# Checks if the user running Vagrant on Windows has administrative
|
|
# privileges.
|
|
#
|
|
# From: https://support.microsoft.com/en-us/kb/243330
|
|
# SID: S-1-5-19
|
|
#
|
|
# @return [Boolean]
|
|
def windows_admin?
|
|
return @_windows_admin if defined?(@_windows_admin)
|
|
|
|
@_windows_admin = -> {
|
|
ps_cmd = '(new-object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)'
|
|
output = Vagrant::Util::PowerShell.execute_cmd(ps_cmd)
|
|
return output == 'True'
|
|
}.call
|
|
|
|
return @_windows_admin
|
|
end
|
|
|
|
# Checks if Hyper-V is accessible to the local user. It will check
|
|
# if user is in the "Hyper-V Administrators" group, is a Domain
|
|
# administrator, and finally will run a manual interaction with
|
|
# Hyper-V to determine if Hyper-V is usable for the current user.
|
|
#
|
|
# From: https://support.microsoft.com/en-us/kb/243330
|
|
# SID: S-1-5-32-578
|
|
# Name: BUILTIN\Hyper-V Administrators
|
|
# SID: S-1-5-21DOMAIN-512
|
|
# Name: Domain Admins
|
|
#
|
|
# @return [Boolean]
|
|
def windows_hyperv_admin?
|
|
return @_windows_hyperv_admin if defined?(@_windows_hyperv_admin)
|
|
|
|
if ENV["VAGRANT_IS_HYPERV_ADMIN"]
|
|
return @_windows_hyperv_admin = true
|
|
end
|
|
|
|
ps_cmd = "Write-Output ([System.Security.Principal.WindowsIdentity]::GetCurrent().Groups | " \
|
|
"Select-Object Value | ConvertTo-JSON)"
|
|
output = Vagrant::Util::PowerShell.execute_cmd(ps_cmd)
|
|
if output
|
|
groups = begin
|
|
JSON.load(output)
|
|
rescue JSON::ParserError
|
|
[]
|
|
end
|
|
admin_group = groups.detect do |g|
|
|
g["Value"].to_s == "S-1-5-32-578" ||
|
|
(g["Value"].start_with?("S-1-5-21") && g["Value"].to_s.end_with?("-512"))
|
|
end
|
|
|
|
if admin_group
|
|
return @_windows_hyperv_admin = true
|
|
end
|
|
end
|
|
|
|
ps_cmd = "$x = (Get-VMHost).Name; if($x -eq [System.Net.Dns]::GetHostName()){ Write-Output 'true'}"
|
|
output = Vagrant::Util::PowerShell.execute_cmd(ps_cmd)
|
|
result = output == "true"
|
|
|
|
return @_windows_hyperv_admin = result
|
|
end
|
|
|
|
# Checks if Hyper-V is enabled on the host system and returns true
|
|
# if enabled.
|
|
#
|
|
# @return [Boolean]
|
|
def windows_hyperv_enabled?
|
|
return @_windows_hyperv_enabled if defined?(@_windows_hyperv_enabled)
|
|
|
|
@_windows_hyperv_enabled = -> {
|
|
check_commands = Array.new.tap do |c|
|
|
c << "(Get-WindowsOptionalFeature -FeatureName Microsoft-Hyper-V-Hypervisor -Online).State"
|
|
c << "(Get-WindowsFeature -FeatureName Microsoft-Hyper-V-Hypervisor).State"
|
|
end
|
|
check_commands.each do |ps_cmd|
|
|
begin
|
|
output = Vagrant::Util::PowerShell.execute_cmd(ps_cmd)
|
|
return true if output == "Enabled"
|
|
rescue Errors::PowerShellInvalidVersion
|
|
logger.warn("Invalid PowerShell version detected during Hyper-V enable check")
|
|
return false
|
|
rescue Errors::PowerShellError
|
|
logger.warn("Powershell command not found or error on execution of command")
|
|
return false
|
|
end
|
|
end
|
|
return false
|
|
}.call
|
|
|
|
return @_windows_hyperv_enabled
|
|
end
|
|
|
|
# This takes any path and converts it from a Windows path to a
|
|
# Cygwin style path.
|
|
#
|
|
# @param [String] path
|
|
# @return [String]
|
|
def cygwin_path(path)
|
|
begin
|
|
cygpath = Vagrant::Util::Which.which("cygpath")
|
|
if cygpath.nil?
|
|
# If Which can't find it, just attempt to invoke it directly
|
|
cygpath = "cygpath"
|
|
else
|
|
cygpath.gsub!("/", '\\')
|
|
end
|
|
|
|
process = Subprocess.execute(
|
|
cygpath, "-u", "-a", path.to_s)
|
|
return process.stdout.chomp
|
|
rescue Errors::CommandUnavailableWindows => e
|
|
# Sometimes cygpath isn't available (msys). Instead, do what we
|
|
# can with bash tricks.
|
|
process = Subprocess.execute(
|
|
"bash",
|
|
"--noprofile",
|
|
"--norc",
|
|
"-c", "cd #{Shellwords.escape(path)} && pwd")
|
|
return process.stdout.chomp
|
|
end
|
|
end
|
|
|
|
# This takes any path and converts it from a Windows path to a
|
|
# msys style path.
|
|
#
|
|
# @param [String] path
|
|
# @return [String]
|
|
def msys_path(path)
|
|
begin
|
|
# We have to revert to the old env
|
|
# path here, otherwise it looks like
|
|
# msys2 ends up using the wrong cygpath
|
|
# binary and ends up with a `/cygdrive`
|
|
# when it doesn't exist in msys2
|
|
original_path_env = ENV['PATH']
|
|
ENV['PATH'] = ENV['VAGRANT_OLD_ENV_PATH']
|
|
cygwin_path(path)
|
|
ensure
|
|
ENV['PATH'] = original_path_env
|
|
end
|
|
end
|
|
|
|
# This takes any path and converts it to a full-length Windows
|
|
# path on Windows machines in Cygwin.
|
|
#
|
|
# @return [String]
|
|
def cygwin_windows_path(path)
|
|
return path if !cygwin?
|
|
|
|
# Replace all "\" with "/", otherwise cygpath doesn't work.
|
|
path = unix_windows_path(path)
|
|
|
|
# Call out to cygpath and gather the result
|
|
process = Subprocess.execute("cygpath", "-w", "-l", "-a", path.to_s)
|
|
return process.stdout.chomp
|
|
end
|
|
|
|
# This takes any path and converts Windows-style path separators
|
|
# to Unix-like path separators.
|
|
# @return [String]
|
|
def unix_windows_path(path)
|
|
path.gsub("\\", "/")
|
|
end
|
|
|
|
# This checks if the filesystem is case sensitive. This is not a
|
|
# 100% correct check, since it is possible that the temporary
|
|
# directory runs a different filesystem than the root directory.
|
|
# However, this works in many cases.
|
|
def fs_case_sensitive?
|
|
return @_fs_case_sensitive if defined?(@_fs_case_sensitive)
|
|
@_fs_case_sensitive = Dir.mktmpdir("vagrant-fs-case-sensitive") do |dir|
|
|
tmp_file = File.join(dir, "FILE")
|
|
File.open(tmp_file, "w") do |f|
|
|
f.write("foo")
|
|
end
|
|
|
|
# The filesystem is case sensitive if the lowercased version
|
|
# of the filename is NOT reported as existing.
|
|
!File.file?(File.join(dir, "file"))
|
|
end
|
|
return @_fs_case_sensitive
|
|
end
|
|
|
|
# This expands the path and ensures proper casing of each part
|
|
# of the path.
|
|
def fs_real_path(path, **opts)
|
|
path = Pathname.new(File.expand_path(path))
|
|
|
|
if path.exist? && !fs_case_sensitive?
|
|
# If the path contains a Windows short path, then we attempt to
|
|
# expand. The require below is embedded here since it requires
|
|
# windows to work.
|
|
if windows? && path.to_s =~ /~\d(\/|\\)/
|
|
require_relative "windows_path"
|
|
path = Pathname.new(WindowsPath.longname(path.to_s))
|
|
end
|
|
|
|
# Build up all the parts of the path
|
|
original = []
|
|
while !path.root?
|
|
original.unshift(path.basename.to_s)
|
|
path = path.parent
|
|
end
|
|
|
|
# Traverse each part and join it into the resulting path
|
|
original.each do |single|
|
|
Dir.entries(path).each do |entry|
|
|
begin
|
|
single = single.encode("filesystem").to_s
|
|
rescue ArgumentError => err
|
|
Vagrant.global_logger.warn("path encoding failed - part=#{single} err=#{err.class} msg=#{err}")
|
|
# NOTE: Depending on the Windows environment the above
|
|
# encode will generate an "input string invalid" when
|
|
# attempting to encode. If that happens, continue on
|
|
end
|
|
if entry.downcase == single.downcase
|
|
path = path.join(entry)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if windows?
|
|
# Fix the drive letter to be uppercase.
|
|
path = path.to_s
|
|
if path[1] == ":"
|
|
path[0] = path[0].upcase
|
|
end
|
|
|
|
path = Pathname.new(path)
|
|
end
|
|
|
|
path
|
|
end
|
|
|
|
# Converts a given path to UNC format by adding a prefix and converting slashes.
|
|
# @param [String] path Path to convert to UNC for Windows
|
|
# @return [String]
|
|
def windows_unc_path(path)
|
|
path = path.gsub("/", "\\")
|
|
|
|
# Convert to UNC path
|
|
if path =~ /^[a-zA-Z]:\\?$/
|
|
# If the path is just a drive letter, then return that as-is
|
|
path + "\\"
|
|
elsif path.start_with?("\\\\")
|
|
# If the path already starts with `\\` assume UNC and return as-is
|
|
path
|
|
else
|
|
"\\\\?\\" + path.gsub("/", "\\")
|
|
end
|
|
end
|
|
|
|
# Returns a boolean noting whether the terminal supports color.
|
|
# output.
|
|
def terminal_supports_colors?
|
|
return @_terminal_supports_colors if defined?(@_terminal_supports_colors)
|
|
@_terminal_supports_colors = -> {
|
|
if windows?
|
|
return true if ENV.key?("ANSICON")
|
|
return true if cygwin?
|
|
return true if ENV["TERM"] == "cygwin"
|
|
return false
|
|
end
|
|
|
|
return true
|
|
}.call
|
|
return @_terminal_supports_colors
|
|
end
|
|
|
|
def platform
|
|
return @_platform if defined?(@_platform)
|
|
@_platform = RbConfig::CONFIG["host_os"].downcase
|
|
return @_platform
|
|
end
|
|
|
|
# Determine if given path is within the WSL rootfs. Returns
|
|
# true if within the subsystem, or false if outside the subsystem.
|
|
#
|
|
# @param [String] path Path to check
|
|
# @return [Boolean] path is within subsystem
|
|
def wsl_path?(path)
|
|
wsl? && !path.to_s.downcase.start_with?("/mnt/")
|
|
end
|
|
|
|
# Compute the path to rootfs of currently active WSL.
|
|
#
|
|
# @return [String] A path to rootfs of a current WSL instance.
|
|
def wsl_rootfs
|
|
return @_wsl_rootfs if defined?(@_wsl_rootfs)
|
|
|
|
if wsl?
|
|
# Mark our filesystem with a temporary file having an unique name.
|
|
marker = Tempfile.new(Time.now.to_i.to_s)
|
|
logger = Log4r::Logger.new("vagrant::util::platform::wsl")
|
|
|
|
# Check for lxrun installation first
|
|
lxrun_path = [wsl_windows_appdata_local, "lxss"].join("\\")
|
|
paths = [lxrun_path]
|
|
|
|
logger.debug("checking registry for WSL installation path")
|
|
paths += PowerShell.execute_cmd(
|
|
'(Get-ChildItem HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss ' \
|
|
'| ForEach-Object {Get-ItemProperty $_.PSPath}).BasePath').to_s.split("\r\n").map(&:strip)
|
|
paths.delete_if{|path| path.to_s.empty?}
|
|
|
|
paths.each do |path|
|
|
# Lowercase the drive letter, skip the next symbol (which is a
|
|
# colon from a Windows path) and convert path to UNIX style.
|
|
check_path = "/mnt/#{path[0, 1].downcase}#{path[2..-1].tr('\\', '/')}/rootfs"
|
|
begin
|
|
process = Subprocess.execute("wslpath", "-u", "-a", path)
|
|
check_path = "#{process.stdout.chomp}/rootfs" if process.exit_code == 0
|
|
rescue Errors::CommandUnavailable => e
|
|
# pass
|
|
end
|
|
|
|
logger.debug("checking `#{path}` for current WSL instance")
|
|
begin
|
|
# https://blogs.msdn.microsoft.com/wsl/2016/06/15/wsl-file-system-support
|
|
# Current WSL instance doesn't have an access to its mount from
|
|
# within itself despite all others are available. That's the
|
|
# hacky way we're using to determine current instance.
|
|
# For example we have three WSL instances:
|
|
# A -> C:\User\USER\AppData\Local\Packages\A\LocalState\rootfs
|
|
# B -> C:\User\USER\AppData\Local\Packages\B\LocalState\rootfs
|
|
# C -> C:\User\USER\AppData\Local\Packages\C\LocalState\rootfs
|
|
# If we're in "A" WSL at the moment, then its path will not be
|
|
# accessible since it's mounted for exactly the instance we're
|
|
# in. All others can be opened.
|
|
Dir.open(check_path) do |fs|
|
|
# A fallback for a case if our trick will stop working. For
|
|
# that we've created a temporary file with an unique name in
|
|
# a current WSL and now seeking it among all WSL.
|
|
if File.exist?("#{fs.path}/#{marker.path}")
|
|
@_wsl_rootfs = path
|
|
break
|
|
end
|
|
end
|
|
rescue Errno::EACCES
|
|
@_wsl_rootfs = path
|
|
# You can create and simultaneously run multiple WSL instances,
|
|
# comment out the "break", run this script within each one and
|
|
# it'll return only single value.
|
|
break
|
|
rescue Errno::ENOENT
|
|
# Warn about data discrepancy between Winreg and file system
|
|
# states. For the sake of justice, it's worth mentioning that
|
|
# it is possible only when someone will manually break WSL by
|
|
# removing a directory of its base path (kinda "stupid WSL
|
|
# uninstallation by removing hidden and system directory").
|
|
logger.warn("WSL instance at `#{path} is broken or no longer exists")
|
|
end
|
|
# All other exceptions have to be raised since they will mean
|
|
# something unpredictably terrible.
|
|
end
|
|
|
|
marker.close!
|
|
|
|
raise Vagrant::Errors::WSLRootFsNotFoundError if @_wsl_rootfs.nil?
|
|
end
|
|
|
|
# Attach the rootfs leaf to the path
|
|
if @_wsl_rootfs != lxrun_path
|
|
@_wsl_rootfs = "#{@_wsl_rootfs}\\rootfs"
|
|
end
|
|
|
|
logger.debug("detected `#{@_wsl_rootfs}` as current WSL instance")
|
|
|
|
@_wsl_rootfs
|
|
end
|
|
|
|
# Convert a WSL path to the local Windows path. This is useful
|
|
# for conversion when calling out to Windows executables from
|
|
# the WSL
|
|
#
|
|
# @param [String, Pathname] path Path to convert
|
|
# @return [String]
|
|
def wsl_to_windows_path(path)
|
|
path = path.to_s
|
|
if wsl? && wsl_windows_access? && !path.match(/^[a-zA-Z]:/)
|
|
path = File.expand_path(path)
|
|
begin
|
|
process = Subprocess.execute("wslpath", "-w", "-a", path)
|
|
return process.stdout.chomp if process.exit_code == 0
|
|
rescue Errors::CommandUnavailable => e
|
|
# pass
|
|
end
|
|
if wsl_path?(path)
|
|
parts = path.split("/")
|
|
parts.delete_if(&:empty?)
|
|
root_path = wsl_rootfs
|
|
# lxrun splits home separate so we need to account
|
|
# for it's specialness here when we build the path
|
|
if root_path.end_with?("lxss") && !(["root", "home"].include?(parts.first))
|
|
root_path = "#{root_path}\\rootfs"
|
|
end
|
|
path = [root_path, *parts].join("\\")
|
|
else
|
|
path = path.sub("/mnt/", "")
|
|
parts = path.split("/")
|
|
parts.first << ":"
|
|
path = parts.join("\\")
|
|
end
|
|
end
|
|
path
|
|
end
|
|
|
|
# Takes a windows path and formats it to the
|
|
# 'unix' style (i.e. `/cygdrive/c` or `/c/`)
|
|
#
|
|
# @param [Pathname, String] path Path to convert
|
|
# @param [Hash] hash of arguments
|
|
# @return [String]
|
|
def format_windows_path(path, *args)
|
|
path = cygwin_path(path) if cygwin?
|
|
path = msys_path(path) if msys?
|
|
path = wsl_to_windows_path(path) if wsl?
|
|
if windows? || wsl?
|
|
path = windows_unc_path(path) if !args.include?(:disable_unc)
|
|
end
|
|
|
|
path
|
|
end
|
|
|
|
# Automatically convert a given path to a Windows path. Will only
|
|
# be applied if running on a Windows host. If running on Windows
|
|
# host within the WSL, the actual Windows path will be returned.
|
|
#
|
|
# @param [Pathname, String] path Path to convert
|
|
# @return [String]
|
|
def windows_path(path)
|
|
path = cygwin_windows_path(path)
|
|
path = wsl_to_windows_path(path)
|
|
if windows? || wsl?
|
|
path = windows_unc_path(path)
|
|
end
|
|
path
|
|
end
|
|
|
|
# Allow Vagrant to access Vagrant managed machines outside the
|
|
# Windows Subsystem for Linux
|
|
#
|
|
# @return [Boolean]
|
|
def wsl_windows_access?
|
|
if !defined?(@_wsl_windows_access)
|
|
@_wsl_windows_access = wsl? && ENV["VAGRANT_WSL_ENABLE_WINDOWS_ACCESS"]
|
|
end
|
|
@_wsl_windows_access
|
|
end
|
|
|
|
# The allowed windows system path Vagrant can manage from the Windows
|
|
# Subsystem for Linux
|
|
#
|
|
# @return [Pathname]
|
|
def wsl_windows_accessible_path
|
|
if !defined?(@_wsl_windows_accessible_path)
|
|
access_path = ENV["VAGRANT_WSL_WINDOWS_ACCESS_USER_HOME_PATH"]
|
|
if access_path.to_s.empty?
|
|
begin
|
|
process = Subprocess.execute("wslpath", "-u", "-a", wsl_windows_home)
|
|
access_path = process.stdout.chomp if process.exit_code == 0
|
|
rescue Errors::CommandUnavailable => e
|
|
# pass
|
|
end
|
|
end
|
|
if access_path.to_s.empty?
|
|
access_path = wsl_windows_home.gsub("\\", "/").sub(":", "")
|
|
access_path[0] = access_path[0].downcase
|
|
access_path = "/mnt/#{access_path}"
|
|
end
|
|
@_wsl_windows_accessible_path = Pathname.new(access_path)
|
|
end
|
|
@_wsl_windows_accessible_path
|
|
end
|
|
|
|
# Checks given path to determine if Vagrant is allowed to bypass checks
|
|
#
|
|
# @param [String] path Path to check
|
|
# @return [Boolean] Vagrant is allowed to bypass checks
|
|
def wsl_windows_access_bypass?(path)
|
|
wsl? && wsl_windows_access? &&
|
|
path.to_s.start_with?(wsl_windows_accessible_path.to_s)
|
|
end
|
|
|
|
# Mount pattern for extracting local mount information
|
|
MOUNT_PATTERN = /^(?<device>.+?) on (?<mount>.+?) type (?<type>.+?) \((?<options>.+)\)/.freeze
|
|
|
|
# Get list of local mount paths that are DrvFs file systems
|
|
#
|
|
# @return [Array<String>]
|
|
# @todo(chrisroberts): Constantize types for check
|
|
def wsl_drvfs_mounts
|
|
if !defined?(@_wsl_drvfs_mounts)
|
|
@_wsl_drvfs_mounts = []
|
|
if wsl?
|
|
result = Util::Subprocess.execute("mount")
|
|
result.stdout.each_line do |line|
|
|
info = line.match(MOUNT_PATTERN)
|
|
if info && (info[:type] == "drvfs" || info[:type] == "9p")
|
|
@_wsl_drvfs_mounts << info[:mount]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
@_wsl_drvfs_mounts
|
|
end
|
|
|
|
# Check if given path is located on DrvFs file system
|
|
#
|
|
# @param [String, Pathname] path Path to check
|
|
# @return [Boolean]
|
|
def wsl_drvfs_path?(path)
|
|
if wsl?
|
|
wsl_drvfs_mounts.each do |mount_path|
|
|
return true if path.to_s.start_with?(mount_path)
|
|
end
|
|
end
|
|
false
|
|
end
|
|
|
|
# If running within the Windows Subsystem for Linux, this will provide
|
|
# simple setup to allow sharing of the user's VAGRANT_HOME directory
|
|
# within the subsystem
|
|
#
|
|
# @param [Environment] env
|
|
# @param [Logger] logger Optional logger to display information
|
|
def wsl_init(env, logger=nil)
|
|
if wsl?
|
|
if ENV["VAGRANT_WSL_ENABLE_WINDOWS_ACCESS"]
|
|
wsl_validate_matching_vagrant_versions!
|
|
shared_user = ENV["VAGRANT_WSL_WINDOWS_ACCESS_USER"]
|
|
if shared_user.to_s.empty?
|
|
shared_user = wsl_windows_username
|
|
end
|
|
if logger
|
|
logger.warn("Windows Subsystem for Linux detected. Allowing access to user: #{shared_user}")
|
|
logger.warn("Vagrant will be allowed to control Vagrant managed machines within the user's home path.")
|
|
end
|
|
if ENV["VAGRANT_HOME"] || ENV["VAGRANT_WSL_DISABLE_VAGRANT_HOME"]
|
|
logger.warn("VAGRANT_HOME environment variable already set. Not overriding!") if logger
|
|
else
|
|
home_path = wsl_windows_accessible_path.to_s
|
|
ENV["VAGRANT_HOME"] = File.join(home_path, ".vagrant.d")
|
|
if logger
|
|
logger.info("Overriding VAGRANT_HOME environment variable to configured windows user. (#{ENV["VAGRANT_HOME"]})")
|
|
end
|
|
true
|
|
end
|
|
else
|
|
if env.local_data_path.to_s.start_with?("/mnt/")
|
|
raise Vagrant::Errors::WSLVagrantAccessError
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
# Fetch the Windows username currently in use
|
|
#
|
|
# @return [String, Nil]
|
|
def wsl_windows_username
|
|
if !@_wsl_windows_username
|
|
result = Util::Subprocess.execute("cmd.exe", "/c", "echo %USERNAME%")
|
|
if result.exit_code == 0
|
|
@_wsl_windows_username = result.stdout.strip
|
|
end
|
|
end
|
|
@_wsl_windows_username
|
|
end
|
|
|
|
# Fetch the Windows user home directory
|
|
#
|
|
# @return [String, Nil]
|
|
def wsl_windows_home
|
|
if !@_wsl_windows_home
|
|
result = Util::Subprocess.execute("cmd.exe", "/c" "echo %USERPROFILE%")
|
|
if result.exit_code == 0
|
|
@_wsl_windows_home = result.stdout.gsub("\"", "").strip
|
|
end
|
|
end
|
|
@_wsl_windows_home
|
|
end
|
|
|
|
# Fetch the Windows user local app data directory
|
|
#
|
|
# @return [String, Nil]
|
|
def wsl_windows_appdata_local
|
|
if !@_wsl_windows_appdata_local
|
|
result = Util::Subprocess.execute("cmd.exe", "/c", "echo %LOCALAPPDATA%")
|
|
if result.exit_code == 0
|
|
@_wsl_windows_appdata_local = result.stdout.gsub("\"", "").strip
|
|
end
|
|
end
|
|
@_wsl_windows_appdata_local
|
|
end
|
|
|
|
# Confirm Vagrant versions installed within the WSL and the Windows system
|
|
# are the same. Raise error if they do not match.
|
|
def wsl_validate_matching_vagrant_versions!
|
|
valid = false
|
|
if Util::Which.which("vagrant.exe")
|
|
result = Util::Subprocess.execute("vagrant.exe", "--version")
|
|
if result.exit_code == 0
|
|
windows_version = result.stdout.match(/Vagrant (?<version>[\w.-]+)/)
|
|
if windows_version
|
|
windows_version = windows_version[:version].strip
|
|
valid = windows_version == Vagrant::VERSION
|
|
end
|
|
end
|
|
if !valid
|
|
raise Vagrant::Errors::WSLVagrantVersionMismatch,
|
|
wsl_version: Vagrant::VERSION,
|
|
windows_version: windows_version || "unknown"
|
|
end
|
|
end
|
|
end
|
|
|
|
# systemd is in use
|
|
def systemd?
|
|
if !defined?(@_systemd)
|
|
if !windows?
|
|
result = Vagrant::Util::Subprocess.execute("ps", "-o", "comm=", "1")
|
|
@_systemd = result.stdout.chomp == "systemd"
|
|
else
|
|
@_systemd = false
|
|
end
|
|
end
|
|
@_systemd
|
|
end
|
|
|
|
# @private
|
|
# Reset the cached values for platform. This is not considered a public
|
|
# API and should only be used for testing.
|
|
def reset!
|
|
instance_variables.each(&method(:remove_instance_variable))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|