"Fossies" - the Fresh Open Source Software Archive

Member "vagrant-2.2.14/lib/vagrant/util/platform.rb" (20 Nov 2020, 26389 Bytes) of package /linux/misc/vagrant-2.2.14.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Ruby source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "platform.rb": 2.2.13_vs_2.2.14.

    1 require "rbconfig"
    2 require "shellwords"
    3 require "tempfile"
    4 require "tmpdir"
    5 require "log4r"
    6 
    7 require "vagrant/util/subprocess"
    8 require "vagrant/util/powershell"
    9 require "vagrant/util/which"
   10 
   11 module Vagrant
   12   module Util
   13     # This class just contains some platform checking code.
   14     class Platform
   15       class << self
   16 
   17         def logger
   18           if !defined?(@_logger)
   19             @_logger = Log4r::Logger.new("vagrant::util::platform")
   20           end
   21           @_logger
   22         end
   23 
   24         def cygwin?
   25           if !defined?(@_cygwin)
   26             @_cygwin = ENV["VAGRANT_DETECTED_OS"].to_s.downcase.include?("cygwin") ||
   27               platform.include?("cygwin") ||
   28               ENV["OSTYPE"].to_s.downcase.include?("cygwin")
   29           end
   30           @_cygwin
   31         end
   32 
   33         def msys?
   34           if !defined?(@_msys)
   35             @_msys = ENV["VAGRANT_DETECTED_OS"].to_s.downcase.include?("msys") ||
   36               platform.include?("msys") ||
   37               ENV["OSTYPE"].to_s.downcase.include?("msys")
   38           end
   39           @_msys
   40         end
   41 
   42         def wsl?
   43           if !defined?(@_wsl)
   44             @_wsl = false
   45             SilenceWarnings.silence! do
   46               # Find 'microsoft' in /proc/version indicative of WSL
   47               if File.file?('/proc/version')
   48                 osversion = File.open('/proc/version', &:gets)
   49                 if osversion.downcase.include?("microsoft")
   50                   @_wsl = true
   51                 end
   52               end
   53             end
   54           end
   55           @_wsl
   56         end
   57 
   58         [:darwin, :bsd, :freebsd, :linux, :solaris].each do |type|
   59           define_method("#{type}?") do
   60             platform.include?(type.to_s)
   61           end
   62         end
   63 
   64         def windows?
   65           return @_windows if defined?(@_windows)
   66           @_windows = %w[mingw mswin].any? { |t| platform.include?(t) }
   67           return @_windows
   68         end
   69 
   70         # Checks if the user running Vagrant on Windows has administrative
   71         # privileges.
   72         #
   73         # From: https://support.microsoft.com/en-us/kb/243330
   74         # SID: S-1-5-19
   75         #
   76         # @return [Boolean]
   77         def windows_admin?
   78           return @_windows_admin if defined?(@_windows_admin)
   79 
   80           @_windows_admin = -> {
   81             ps_cmd = '(new-object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)'
   82             output = Vagrant::Util::PowerShell.execute_cmd(ps_cmd)
   83             return output == 'True'
   84           }.call
   85 
   86           return @_windows_admin
   87         end
   88 
   89         # Checks if Hyper-V is accessible to the local user. It will check
   90         # if user is in the "Hyper-V Administrators" group, is a Domain
   91         # administrator, and finally will run a manual interaction with
   92         # Hyper-V to determine if Hyper-V is usable for the current user.
   93         #
   94         # From: https://support.microsoft.com/en-us/kb/243330
   95         # SID: S-1-5-32-578
   96         # Name: BUILTIN\Hyper-V Administrators
   97         # SID: S-1-5-21DOMAIN-512
   98         # Name: Domain Admins
   99         #
  100         # @return [Boolean]
  101         def windows_hyperv_admin?
  102           return @_windows_hyperv_admin if defined?(@_windows_hyperv_admin)
  103 
  104           if ENV["VAGRANT_IS_HYPERV_ADMIN"]
  105             return @_windows_hyperv_admin = true
  106           end
  107 
  108           ps_cmd = "Write-Output ([System.Security.Principal.WindowsIdentity]::GetCurrent().Groups | " \
  109             "Select-Object Value | ConvertTo-JSON)"
  110           output = Vagrant::Util::PowerShell.execute_cmd(ps_cmd)
  111           if output
  112             groups = begin
  113                        JSON.load(output)
  114                      rescue JSON::ParserError
  115                        []
  116                      end
  117             admin_group = groups.detect do |g|
  118               g["Value"].to_s == "S-1-5-32-578" ||
  119                 (g["Value"].start_with?("S-1-5-21") && g["Value"].to_s.end_with?("-512"))
  120             end
  121 
  122             if admin_group
  123               return @_windows_hyperv_admin = true
  124             end
  125           end
  126 
  127           ps_cmd = "$x = (Get-VMHost).Name; if($x -eq [System.Net.Dns]::GetHostName()){ Write-Output 'true'}"
  128           output = Vagrant::Util::PowerShell.execute_cmd(ps_cmd)
  129           result = output == "true"
  130 
  131           return @_windows_hyperv_admin = result
  132         end
  133 
  134         # Checks if Hyper-V is enabled on the host system and returns true
  135         # if enabled.
  136         #
  137         # @return [Boolean]
  138         def windows_hyperv_enabled?
  139           return @_windows_hyperv_enabled if defined?(@_windows_hyperv_enabled)
  140 
  141           @_windows_hyperv_enabled = -> {
  142             ["Get-WindowsOptionalFeature", "Get-WindowsFeature"].each do |cmd_name|
  143               ps_cmd = "$(#{cmd_name} -FeatureName Microsoft-Hyper-V-Hypervisor).State"
  144               begin
  145                 output = Vagrant::Util::PowerShell.execute_cmd(ps_cmd)
  146                 return true if output == "Enabled"
  147               rescue Errors::PowerShellInvalidVersion
  148                 logger.warn("Invalid PowerShell version detected during Hyper-V enable check")
  149                 return false
  150               end
  151             end
  152             return false
  153           }.call
  154 
  155           return @_windows_hyperv_enabled
  156         end
  157 
  158         # This takes any path and converts it from a Windows path to a
  159         # Cygwin style path.
  160         #
  161         # @param [String] path
  162         # @return [String]
  163         def cygwin_path(path)
  164           begin
  165             cygpath = Vagrant::Util::Which.which("cygpath")
  166             if cygpath.nil?
  167               # If Which can't find it, just attempt to invoke it directly
  168               cygpath = "cygpath"
  169             else
  170               cygpath.gsub!("/", '\\')
  171             end
  172 
  173             process = Subprocess.execute(
  174               cygpath, "-u", "-a", path.to_s)
  175             return process.stdout.chomp
  176           rescue Errors::CommandUnavailableWindows => e
  177             # Sometimes cygpath isn't available (msys). Instead, do what we
  178             # can with bash tricks.
  179             process = Subprocess.execute(
  180               "bash",
  181               "--noprofile",
  182               "--norc",
  183               "-c", "cd #{Shellwords.escape(path)} && pwd")
  184             return process.stdout.chomp
  185           end
  186         end
  187 
  188         # This takes any path and converts it from a Windows path to a
  189         # msys style path.
  190         #
  191         # @param [String] path
  192         # @return [String]
  193         def msys_path(path)
  194           begin
  195             # We have to revert to the old env
  196             # path here, otherwise it looks like
  197             # msys2 ends up using the wrong cygpath
  198             # binary and ends up with a `/cygdrive`
  199             # when it doesn't exist in msys2
  200             original_path_env = ENV['PATH']
  201             ENV['PATH'] = ENV['VAGRANT_OLD_ENV_PATH']
  202             cygwin_path(path)
  203           ensure
  204             ENV['PATH'] = original_path_env
  205           end
  206         end
  207 
  208         # This takes any path and converts it to a full-length Windows
  209         # path on Windows machines in Cygwin.
  210         #
  211         # @return [String]
  212         def cygwin_windows_path(path)
  213           return path if !cygwin?
  214 
  215           # Replace all "\" with "/", otherwise cygpath doesn't work.
  216           path = unix_windows_path(path)
  217 
  218           # Call out to cygpath and gather the result
  219           process = Subprocess.execute("cygpath", "-w", "-l", "-a", path.to_s)
  220           return process.stdout.chomp
  221         end
  222 
  223         # This takes any path and converts Windows-style path separators
  224         # to Unix-like path separators.
  225         # @return [String]
  226         def unix_windows_path(path)
  227           path.gsub("\\", "/")
  228         end
  229 
  230         # This checks if the filesystem is case sensitive. This is not a
  231         # 100% correct check, since it is possible that the temporary
  232         # directory runs a different filesystem than the root directory.
  233         # However, this works in many cases.
  234         def fs_case_sensitive?
  235           return @_fs_case_sensitive if defined?(@_fs_case_sensitive)
  236           @_fs_case_sensitive = Dir.mktmpdir("vagrant-fs-case-sensitive") do |dir|
  237             tmp_file = File.join(dir, "FILE")
  238             File.open(tmp_file, "w") do |f|
  239               f.write("foo")
  240             end
  241 
  242             # The filesystem is case sensitive if the lowercased version
  243             # of the filename is NOT reported as existing.
  244             !File.file?(File.join(dir, "file"))
  245           end
  246           return @_fs_case_sensitive
  247         end
  248 
  249         # This expands the path and ensures proper casing of each part
  250         # of the path.
  251         def fs_real_path(path, **opts)
  252           path = Pathname.new(File.expand_path(path))
  253 
  254           if path.exist? && !fs_case_sensitive?
  255             # If the path contains a Windows short path, then we attempt to
  256             # expand. The require below is embedded here since it requires
  257             # windows to work.
  258             if windows? && path.to_s =~ /~\d(\/|\\)/
  259               require_relative "windows_path"
  260               path = Pathname.new(WindowsPath.longname(path.to_s))
  261             end
  262 
  263             # Build up all the parts of the path
  264             original = []
  265             while !path.root?
  266               original.unshift(path.basename.to_s)
  267               path = path.parent
  268             end
  269 
  270             # Traverse each part and join it into the resulting path
  271             original.each do |single|
  272               Dir.entries(path).each do |entry|
  273                 begin
  274                   single = single.encode("filesystem").to_s
  275                 rescue ArgumentError => err
  276                   Vagrant.global_logger.warn("path encoding failed - part=#{single} err=#{err.class} msg=#{err}")
  277                   # NOTE: Depending on the Windows environment the above
  278                   # encode will generate an "input string invalid" when
  279                   # attempting to encode. If that happens, continue on
  280                 end
  281                 if entry.downcase == single.downcase
  282                   path = path.join(entry)
  283                 end
  284               end
  285             end
  286           end
  287 
  288           if windows?
  289             # Fix the drive letter to be uppercase.
  290             path = path.to_s
  291             if path[1] == ":"
  292               path[0] = path[0].upcase
  293             end
  294 
  295             path = Pathname.new(path)
  296           end
  297 
  298           path
  299         end
  300 
  301         # Converts a given path to UNC format by adding a prefix and converting slashes.
  302         # @param [String] path Path to convert to UNC for Windows
  303         # @return [String]
  304         def windows_unc_path(path)
  305           path = path.gsub("/", "\\")
  306 
  307           # Convert to UNC path
  308           if path =~ /^[a-zA-Z]:\\?$/
  309             # If the path is just a drive letter, then return that as-is
  310             path + "\\"
  311           elsif path.start_with?("\\\\")
  312             # If the path already starts with `\\` assume UNC and return as-is
  313             path
  314           else
  315             "\\\\?\\" + path.gsub("/", "\\")
  316           end
  317         end
  318 
  319         # Returns a boolean noting whether the terminal supports color.
  320         # output.
  321         def terminal_supports_colors?
  322           return @_terminal_supports_colors if defined?(@_terminal_supports_colors)
  323           @_terminal_supports_colors = -> {
  324             if windows?
  325               return true if ENV.key?("ANSICON")
  326               return true if cygwin?
  327               return true if ENV["TERM"] == "cygwin"
  328               return false
  329             end
  330 
  331             return true
  332           }.call
  333           return @_terminal_supports_colors
  334         end
  335 
  336         def platform
  337           return @_platform if defined?(@_platform)
  338           @_platform = RbConfig::CONFIG["host_os"].downcase
  339           return @_platform
  340         end
  341 
  342         # Determine if given path is within the WSL rootfs. Returns
  343         # true if within the subsystem, or false if outside the subsystem.
  344         #
  345         # @param [String] path Path to check
  346         # @return [Boolean] path is within subsystem
  347         def wsl_path?(path)
  348           wsl? && !path.to_s.downcase.start_with?("/mnt/")
  349         end
  350 
  351         # Compute the path to rootfs of currently active WSL.
  352         #
  353         # @return [String] A path to rootfs of a current WSL instance.
  354         def wsl_rootfs
  355           return @_wsl_rootfs if defined?(@_wsl_rootfs)
  356 
  357           if wsl?
  358             # Mark our filesystem with a temporary file having an unique name.
  359             marker = Tempfile.new(Time.now.to_i.to_s)
  360             logger = Log4r::Logger.new("vagrant::util::platform::wsl")
  361 
  362             # Check for lxrun installation first
  363             lxrun_path = [wsl_windows_appdata_local, "lxss"].join("\\")
  364             paths = [lxrun_path]
  365 
  366             logger.debug("checking registry for WSL installation path")
  367             paths += PowerShell.execute_cmd(
  368               '(Get-ChildItem HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss ' \
  369                 '| ForEach-Object {Get-ItemProperty $_.PSPath}).BasePath').to_s.split("\r\n").map(&:strip)
  370             paths.delete_if{|path| path.to_s.empty?}
  371 
  372             paths.each do |path|
  373               # Lowercase the drive letter, skip the next symbol (which is a
  374               # colon from a Windows path) and convert path to UNIX style.
  375               check_path = "/mnt/#{path[0, 1].downcase}#{path[2..-1].tr('\\', '/')}/rootfs"
  376               begin
  377                 process = Subprocess.execute("wslpath", "-u", "-a", path)
  378                 check_path = "#{process.stdout.chomp}/rootfs" if process.exit_code == 0
  379               rescue Errors::CommandUnavailable => e
  380                 # pass
  381               end
  382 
  383               logger.debug("checking `#{path}` for current WSL instance")
  384               begin
  385                 # https://blogs.msdn.microsoft.com/wsl/2016/06/15/wsl-file-system-support
  386                 # Current WSL instance doesn't have an access to its mount from
  387                 # within itself despite all others are available. That's the
  388                 # hacky way we're using to determine current instance.
  389                 # For example we have three WSL instances:
  390                 # A -> C:\User\USER\AppData\Local\Packages\A\LocalState\rootfs
  391                 # B -> C:\User\USER\AppData\Local\Packages\B\LocalState\rootfs
  392                 # C -> C:\User\USER\AppData\Local\Packages\C\LocalState\rootfs
  393                 # If we're in "A" WSL at the moment, then its path will not be
  394                 # accessible since it's mounted for exactly the instance we're
  395                 # in. All others can be opened.
  396                 Dir.open(check_path) do |fs|
  397                   # A fallback for a case if our trick will stop working. For
  398                   # that we've created a temporary file with an unique name in
  399                   # a current WSL and now seeking it among all WSL.
  400                   if File.exist?("#{fs.path}/#{marker.path}")
  401                     @_wsl_rootfs = path
  402                     break
  403                   end
  404                 end
  405               rescue Errno::EACCES
  406                 @_wsl_rootfs = path
  407                 # You can create and simultaneously run multiple WSL instances,
  408                 # comment out the "break", run this script within each one and
  409                 # it'll return only single value.
  410                 break
  411               rescue Errno::ENOENT
  412                 # Warn about data discrepancy between Winreg and file system
  413                 # states. For the sake of justice, it's worth mentioning that
  414                 # it is possible only when someone will manually break WSL by
  415                 # removing a directory of its base path (kinda "stupid WSL
  416                 # uninstallation by removing hidden and system directory").
  417                 logger.warn("WSL instance at `#{path} is broken or no longer exists")
  418               end
  419               # All other exceptions have to be raised since they will mean
  420               # something unpredictably terrible.
  421             end
  422 
  423             marker.close!
  424 
  425             raise Vagrant::Errors::WSLRootFsNotFoundError if @_wsl_rootfs.nil?
  426           end
  427 
  428           # Attach the rootfs leaf to the path
  429           if @_wsl_rootfs != lxrun_path
  430             @_wsl_rootfs = "#{@_wsl_rootfs}\\rootfs"
  431           end
  432 
  433           logger.debug("detected `#{@_wsl_rootfs}` as current WSL instance")
  434 
  435           @_wsl_rootfs
  436         end
  437 
  438         # Convert a WSL path to the local Windows path. This is useful
  439         # for conversion when calling out to Windows executables from
  440         # the WSL
  441         #
  442         # @param [String, Pathname] path Path to convert
  443         # @return [String]
  444         def wsl_to_windows_path(path)
  445           path = path.to_s
  446           if wsl? && wsl_windows_access? && !path.match(/^[a-zA-Z]:/)
  447             path = File.expand_path(path)
  448             begin
  449               process = Subprocess.execute("wslpath", "-w", "-a", path)
  450               return process.stdout.chomp if process.exit_code == 0
  451             rescue Errors::CommandUnavailable => e
  452               # pass
  453             end
  454             if wsl_path?(path)
  455               parts = path.split("/")
  456               parts.delete_if(&:empty?)
  457               root_path = wsl_rootfs
  458               # lxrun splits home separate so we need to account
  459               # for it's specialness here when we build the path
  460               if root_path.end_with?("lxss") && !(["root", "home"].include?(parts.first))
  461                 root_path = "#{root_path}\\rootfs"
  462               end
  463               path = [root_path, *parts].join("\\")
  464             else
  465               path = path.sub("/mnt/", "")
  466               parts = path.split("/")
  467               parts.first << ":"
  468               path = parts.join("\\")
  469             end
  470           end
  471           path
  472         end
  473 
  474         # Takes a windows path and formats it to the
  475         # 'unix' style (i.e. `/cygdrive/c` or `/c/`)
  476         #
  477         # @param [Pathname, String] path Path to convert
  478         # @param [Hash] hash of arguments
  479         # @return [String]
  480         def format_windows_path(path, *args)
  481           path = cygwin_path(path) if cygwin?
  482           path = msys_path(path) if msys?
  483           path = wsl_to_windows_path(path) if wsl?
  484           if windows? || wsl?
  485             path = windows_unc_path(path) if !args.include?(:disable_unc)
  486           end
  487 
  488           path
  489         end
  490 
  491         # Automatically convert a given path to a Windows path. Will only
  492         # be applied if running on a Windows host. If running on Windows
  493         # host within the WSL, the actual Windows path will be returned.
  494         #
  495         # @param [Pathname, String] path Path to convert
  496         # @return [String]
  497         def windows_path(path)
  498           path = cygwin_windows_path(path)
  499           path = wsl_to_windows_path(path)
  500           if windows? || wsl?
  501             path = windows_unc_path(path)
  502           end
  503           path
  504         end
  505 
  506         # Allow Vagrant to access Vagrant managed machines outside the
  507         # Windows Subsystem for Linux
  508         #
  509         # @return [Boolean]
  510         def wsl_windows_access?
  511           if !defined?(@_wsl_windows_access)
  512             @_wsl_windows_access = wsl? && ENV["VAGRANT_WSL_ENABLE_WINDOWS_ACCESS"]
  513           end
  514           @_wsl_windows_access
  515         end
  516 
  517         # The allowed windows system path Vagrant can manage from the Windows
  518         # Subsystem for Linux
  519         #
  520         # @return [Pathname]
  521         def wsl_windows_accessible_path
  522           if !defined?(@_wsl_windows_accessible_path)
  523             access_path = ENV["VAGRANT_WSL_WINDOWS_ACCESS_USER_HOME_PATH"]
  524             if access_path.to_s.empty?
  525               begin
  526                 process = Subprocess.execute("wslpath", "-u", "-a", wsl_windows_home)
  527                 access_path = process.stdout.chomp if process.exit_code == 0
  528               rescue Errors::CommandUnavailable => e
  529                 # pass
  530               end
  531             end
  532             if access_path.to_s.empty?
  533               access_path = wsl_windows_home.gsub("\\", "/").sub(":", "")
  534               access_path[0] = access_path[0].downcase
  535               access_path = "/mnt/#{access_path}"
  536             end
  537             @_wsl_windows_accessible_path = Pathname.new(access_path)
  538           end
  539           @_wsl_windows_accessible_path
  540         end
  541 
  542         # Checks given path to determine if Vagrant is allowed to bypass checks
  543         #
  544         # @param [String] path Path to check
  545         # @return [Boolean] Vagrant is allowed to bypass checks
  546         def wsl_windows_access_bypass?(path)
  547           wsl? && wsl_windows_access? &&
  548             path.to_s.start_with?(wsl_windows_accessible_path.to_s)
  549         end
  550 
  551         # Mount pattern for extracting local mount information
  552         MOUNT_PATTERN = /^(?<device>.+?) on (?<mount>.+?) type (?<type>.+?) \((?<options>.+)\)/.freeze
  553 
  554         # Get list of local mount paths that are DrvFs file systems
  555         #
  556         # @return [Array<String>]
  557         # @todo(chrisroberts): Constantize types for check
  558         def wsl_drvfs_mounts
  559           if !defined?(@_wsl_drvfs_mounts)
  560             @_wsl_drvfs_mounts = []
  561             if wsl?
  562               result = Util::Subprocess.execute("mount")
  563               result.stdout.each_line do |line|
  564                 info = line.match(MOUNT_PATTERN)
  565                 if info && (info[:type] == "drvfs" || info[:type] == "9p")
  566                   @_wsl_drvfs_mounts << info[:mount]
  567                 end
  568               end
  569             end
  570           end
  571           @_wsl_drvfs_mounts
  572         end
  573 
  574         # Check if given path is located on DrvFs file system
  575         #
  576         # @param [String, Pathname] path Path to check
  577         # @return [Boolean]
  578         def wsl_drvfs_path?(path)
  579           if wsl?
  580             wsl_drvfs_mounts.each do |mount_path|
  581               return true if path.to_s.start_with?(mount_path)
  582             end
  583           end
  584           false
  585         end
  586 
  587         # If running within the Windows Subsystem for Linux, this will provide
  588         # simple setup to allow sharing of the user's VAGRANT_HOME directory
  589         # within the subsystem
  590         #
  591         # @param [Environment] env
  592         # @param [Logger] logger Optional logger to display information
  593         def wsl_init(env, logger=nil)
  594           if wsl?
  595             if ENV["VAGRANT_WSL_ENABLE_WINDOWS_ACCESS"]
  596               wsl_validate_matching_vagrant_versions!
  597               shared_user = ENV["VAGRANT_WSL_WINDOWS_ACCESS_USER"]
  598               if shared_user.to_s.empty?
  599                 shared_user = wsl_windows_username
  600               end
  601               if logger
  602                 logger.warn("Windows Subsystem for Linux detected. Allowing access to user: #{shared_user}")
  603                 logger.warn("Vagrant will be allowed to control Vagrant managed machines within the user's home path.")
  604               end
  605               if ENV["VAGRANT_HOME"] || ENV["VAGRANT_WSL_DISABLE_VAGRANT_HOME"]
  606                 logger.warn("VAGRANT_HOME environment variable already set. Not overriding!") if logger
  607               else
  608                 home_path = wsl_windows_accessible_path.to_s
  609                 ENV["VAGRANT_HOME"] = File.join(home_path, ".vagrant.d")
  610                 if logger
  611                   logger.info("Overriding VAGRANT_HOME environment variable to configured windows user. (#{ENV["VAGRANT_HOME"]})")
  612                 end
  613                 true
  614               end
  615             else
  616               if env.local_data_path.to_s.start_with?("/mnt/")
  617                 raise Vagrant::Errors::WSLVagrantAccessError
  618               end
  619             end
  620           end
  621         end
  622 
  623         # Fetch the Windows username currently in use
  624         #
  625         # @return [String, Nil]
  626         def wsl_windows_username
  627           if !@_wsl_windows_username
  628             result = Util::Subprocess.execute("cmd.exe", "/c", "echo %USERNAME%")
  629             if result.exit_code == 0
  630               @_wsl_windows_username = result.stdout.strip
  631             end
  632           end
  633           @_wsl_windows_username
  634         end
  635 
  636         # Fetch the Windows user home directory
  637         #
  638         # @return [String, Nil]
  639         def wsl_windows_home
  640           if !@_wsl_windows_home
  641             result = Util::Subprocess.execute("cmd.exe", "/c" "echo %USERPROFILE%")
  642             if result.exit_code == 0
  643               @_wsl_windows_home = result.stdout.gsub("\"", "").strip
  644             end
  645           end
  646           @_wsl_windows_home
  647         end
  648 
  649         # Fetch the Windows user local app data directory
  650         #
  651         # @return [String, Nil]
  652         def wsl_windows_appdata_local
  653           if !@_wsl_windows_appdata_local
  654             result = Util::Subprocess.execute("cmd.exe", "/c", "echo %LOCALAPPDATA%")
  655             if result.exit_code == 0
  656               @_wsl_windows_appdata_local = result.stdout.gsub("\"", "").strip
  657             end
  658           end
  659           @_wsl_windows_appdata_local
  660         end
  661 
  662         # Confirm Vagrant versions installed within the WSL and the Windows system
  663         # are the same. Raise error if they do not match.
  664         def wsl_validate_matching_vagrant_versions!
  665           valid = false
  666           if Util::Which.which("vagrant.exe")
  667             result = Util::Subprocess.execute("vagrant.exe", "--version")
  668             if result.exit_code == 0
  669               windows_version = result.stdout.match(/Vagrant (?<version>[\w.-]+)/)
  670               if windows_version
  671                 windows_version = windows_version[:version].strip
  672                 valid = windows_version == Vagrant::VERSION
  673               end
  674             end
  675             if !valid
  676               raise Vagrant::Errors::WSLVagrantVersionMismatch,
  677                 wsl_version: Vagrant::VERSION,
  678                 windows_version: windows_version || "unknown"
  679             end
  680           end
  681         end
  682 
  683         # systemd is in use
  684         def systemd?
  685           if !defined?(@_systemd)
  686             if !windows?
  687               result = Vagrant::Util::Subprocess.execute("ps", "-o", "comm=", "1")
  688               @_systemd = result.stdout.chomp == "systemd"
  689             else
  690               @_systemd = false
  691             end
  692           end
  693           @_systemd
  694         end
  695 
  696         # @private
  697         # Reset the cached values for platform. This is not considered a public
  698         # API and should only be used for testing.
  699         def reset!
  700           instance_variables.each(&method(:remove_instance_variable))
  701         end
  702       end
  703     end
  704   end
  705 end