class Facter::Core::Execution::Base

  def with_env(values)
    old = {}
    values.each do |var, value|
      # save the old value if it exists
      if old_val = ENV[var]
        old[var] = old_val
      end
      # set the new (temporary) value for the environment variable
      ENV[var] = value
    end
    # execute the caller's block, capture the return value
    rv = yield
  # use an ensure block to make absolutely sure we restore the variables
  ensure
    # restore the old values
    values.each do |var, value|
      if old.include?(var)
        ENV[var] = old[var]
      else
        # if there was no old value, delete the key from the current environment variables hash
        ENV.delete(var)
      end
    end
    # return the captured return value
    rv
  end

  def exec(code)

    ## Set LANG to force i18n to C for the duration of this exec; this ensures that any code that parses the
    ## output of the command can expect it to be in a consistent / predictable format / locale
    with_env "LANG" => "C" do

      if expanded_code = expand_command(code)
        # if we can find the binary, we'll run the command with the expanded path to the binary
        code = expanded_code
      else
        return ''
      end

      out = ''

      begin
        wait_for_child = true
        out = %x{#{code}}.chomp
        wait_for_child = false
      rescue => detail
        Facter.warn(detail.message)
        return ''
      ensure
        if wait_for_child
          # We need to ensure that if this code exits early then any spawned
          # children will be reaped. Process execution is frequently
          # terminated using Timeout.timeout but since the timeout isn't in
          # this scope we can't rescue the raised exception. The best that
          # we can do is determine if the child has exited, and if it hasn't
          # then we need to spawn a thread to wait for the child.
          #
          # Due to the limitations of Ruby 1.8 there aren't good ways to
          # asynchronously run a command and grab the PID of that command
          # using the standard library. The best we can do is blindly wait
          # on all processes and hope for the best. This issue is described
          # at https://tickets.puppetlabs.com/browse/FACT-150
          Thread.new { Process.waitall }
        end
      end

      out
    end
  end
end