class Vagrant::Util::Platform

This class just contains some platform checking code.

Constants

MOUNT_PATTERN

Mount pattern for extracting local mount information

Public Class Methods

cygwin?() click to toggle source
# File lib/vagrant/util/platform.rb, line 24
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
cygwin_path(path) click to toggle source

This takes any path and converts it from a Windows path to a Cygwin style path.

@param [String] path @return [String]

# File lib/vagrant/util/platform.rb, line 169
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
cygwin_windows_path(path) click to toggle source

This takes any path and converts it to a full-length Windows path on Windows machines in Cygwin.

@return [String]

# File lib/vagrant/util/platform.rb, line 218
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
format_windows_path(path, *args) click to toggle source

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]

# File lib/vagrant/util/platform.rb, line 486
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
fs_case_sensitive?() click to toggle source

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.

# File lib/vagrant/util/platform.rb, line 240
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
fs_real_path(path, **opts) click to toggle source

This expands the path and ensures proper casing of each part of the path.

# File lib/vagrant/util/platform.rb, line 257
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
logger() click to toggle source
# File lib/vagrant/util/platform.rb, line 17
def logger
  if !defined?(@_logger)
    @_logger = Log4r::Logger.new("vagrant::util::platform")
  end
  @_logger
end
msys?() click to toggle source
# File lib/vagrant/util/platform.rb, line 33
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
msys_path(path) click to toggle source

This takes any path and converts it from a Windows path to a msys style path.

@param [String] path @return [String]

# File lib/vagrant/util/platform.rb, line 199
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
platform() click to toggle source
# File lib/vagrant/util/platform.rb, line 342
def platform
  return @_platform if defined?(@_platform)
  @_platform = RbConfig::CONFIG["host_os"].downcase
  return @_platform
end
reset!() click to toggle source

@private Reset the cached values for platform. This is not considered a public API and should only be used for testing.

# File lib/vagrant/util/platform.rb, line 705
def reset!
  instance_variables.each(&method(:remove_instance_variable))
end
systemd?() click to toggle source

systemd is in use

# File lib/vagrant/util/platform.rb, line 690
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
terminal_supports_colors?() click to toggle source

Returns a boolean noting whether the terminal supports color. output.

# File lib/vagrant/util/platform.rb, line 327
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
unix_windows_path(path) click to toggle source

This takes any path and converts Windows-style path separators to Unix-like path separators. @return [String]

# File lib/vagrant/util/platform.rb, line 232
def unix_windows_path(path)
  path.gsub("\\", "/")
end
windows?() click to toggle source
# File lib/vagrant/util/platform.rb, line 64
def windows?
  return @_windows if defined?(@_windows)
  @_windows = %w[mingw mswin].any? { |t| platform.include?(t) }
  return @_windows
end
windows_admin?() click to toggle source

Checks if the user running Vagrant on Windows has administrative privileges.

From: support.microsoft.com/en-us/kb/243330 SID: S-1-5-19

@return [Boolean]

# File lib/vagrant/util/platform.rb, line 77
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
windows_hyperv_admin?() click to toggle source

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: support.microsoft.com/en-us/kb/243330 SID: S-1-5-32-578 Name: BUILTINHyper-V Administrators SID: S-1-5-21DOMAIN-512 Name: Domain Admins

@return [Boolean]

# File lib/vagrant/util/platform.rb, line 101
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
windows_hyperv_enabled?() click to toggle source

Checks if Hyper-V is enabled on the host system and returns true if enabled.

@return [Boolean]

# File lib/vagrant/util/platform.rb, line 138
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
windows_path(path) click to toggle source

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]

# File lib/vagrant/util/platform.rb, line 503
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
windows_unc_path(path) click to toggle source

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]

# File lib/vagrant/util/platform.rb, line 310
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
wsl?() click to toggle source
# File lib/vagrant/util/platform.rb, line 42
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
wsl_drvfs_mounts() click to toggle source

Get list of local mount paths that are DrvFs file systems

@return [Array<String>] @todo(chrisroberts): Constantize types for check

# File lib/vagrant/util/platform.rb, line 564
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
wsl_drvfs_path?(path) click to toggle source

Check if given path is located on DrvFs file system

@param [String, Pathname] path Path to check @return [Boolean]

# File lib/vagrant/util/platform.rb, line 584
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
wsl_init(env, logger=nil) click to toggle source

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

# File lib/vagrant/util/platform.rb, line 599
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
wsl_path?(path) click to toggle source

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

# File lib/vagrant/util/platform.rb, line 353
def wsl_path?(path)
  wsl? && !path.to_s.downcase.start_with?("/mnt/")
end
wsl_rootfs() click to toggle source

Compute the path to rootfs of currently active WSL.

@return [String] A path to rootfs of a current WSL instance.

# File lib/vagrant/util/platform.rb, line 360
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
wsl_to_windows_path(path) click to toggle source

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]

# File lib/vagrant/util/platform.rb, line 450
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
wsl_validate_matching_vagrant_versions!() click to toggle source

Confirm Vagrant versions installed within the WSL and the Windows system are the same. Raise error if they do not match.

# File lib/vagrant/util/platform.rb, line 670
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
wsl_windows_access?() click to toggle source

Allow Vagrant to access Vagrant managed machines outside the Windows Subsystem for Linux

@return [Boolean]

# File lib/vagrant/util/platform.rb, line 516
def wsl_windows_access?
  if !defined?(@_wsl_windows_access)
    @_wsl_windows_access = wsl? && ENV["VAGRANT_WSL_ENABLE_WINDOWS_ACCESS"]
  end
  @_wsl_windows_access
end
wsl_windows_access_bypass?(path) click to toggle source

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

# File lib/vagrant/util/platform.rb, line 552
def wsl_windows_access_bypass?(path)
  wsl? && wsl_windows_access? &&
    path.to_s.start_with?(wsl_windows_accessible_path.to_s)
end
wsl_windows_accessible_path() click to toggle source

The allowed windows system path Vagrant can manage from the Windows Subsystem for Linux

@return [Pathname]

# File lib/vagrant/util/platform.rb, line 527
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
wsl_windows_appdata_local() click to toggle source

Fetch the Windows user local app data directory

@return [String, Nil]

# File lib/vagrant/util/platform.rb, line 658
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
wsl_windows_home() click to toggle source

Fetch the Windows user home directory

@return [String, Nil]

# File lib/vagrant/util/platform.rb, line 645
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
wsl_windows_username() click to toggle source

Fetch the Windows username currently in use

@return [String, Nil]

# File lib/vagrant/util/platform.rb, line 632
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