require "rbconfig" require "shellwords" require "tmpdir" require "vagrant/util/subprocess" module Vagrant module Util # This class just contains some platform checking code. class Platform class << self def cygwin? return @_cygwin if defined?(@_cygwin) @_cygwin = -> { # Installer detects Cygwin return true if ENV["VAGRANT_DETECTED_OS"] && ENV["VAGRANT_DETECTED_OS"].downcase.include?("cygwin") # Ruby running in Cygwin return true if platform.include?("cygwin") # Heuristic. If the path contains Cygwin, we just assume we're # in Cygwin. It is generally a safe bet. path = ENV["PATH"] || "" return path.include?("cygwin") }.call return @_cygwin 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. # # @return [Boolean] def windows_admin? return @_windows_admin if defined?(@_windows_admin) # We lazily-load this because it is only available on Windows require "win32/registry" # Verify that we have administrative privileges. The odd method of # detecting this is based on this StackOverflow question: # # https://stackoverflow.com/questions/560366/ # detect-if-running-with-administrator-privileges-under-windows-xp @_windows_admin = -> { begin Win32::Registry::HKEY_USERS.open("S-1-5-19") {} # The above doesn't seem to be 100% bullet proof. See GH-5616. return (`reg query HKU\\S-1-5-19 2>&1` =~ /ERROR/).nil? rescue Win32::Registry::Error return false end }.call return @_windows_admin end # Checks if the user running Vagrant on Windows is a member of the # "Hyper-V Administrators" group. # # From: https://support.microsoft.com/en-us/kb/243330 # SID: S-1-5-32-578 # Name: BUILTIN\Hyper-V Administrators # # @return [Boolean] def windows_hyperv_admin? return @_windows_hyperv_admin if defined?(@_windows_hyperv_admin) @_windows_hyperv_admin = -> { begin username = ENV["USERNAME"] process = Subprocess.execute("net", "localgroup", "Hyper-V Administrators") return process.stdout.include?(username) rescue Errors::CommandUnavailableWindows return false end }.call return @_windows_hyperv_admin end # This takes any path and converts it from a Windows path to a # Cygwin or msys style path. # # @param [String] path # @return [String] def cygwin_path(path) if cygwin? begin # First try the real cygpath process = Subprocess.execute("cygpath", "-u", "-a", path.to_s) return process.stdout.chomp rescue Errors::CommandUnavailableWindows end end # 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 # 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 = path.gsub("\\", "/") # Call out to cygpath and gather the result process = Subprocess.execute("cygpath", "-w", "-l", "-a", path.to_s) return process.stdout.chomp 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| if entry.downcase == single.encode('filesystem').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("/", "\\") # If the path is just a drive letter, then return that as-is return path + "\\" if path =~ /^[a-zA-Z]:\\?$/ # Convert to UNC path "\\\\?\\" + path.gsub("/", "\\") 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 # @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