Fix LANG value used for VirtualBox driver

The VirtualBox driver sets the LANG env var to prevent localized output
being returned when executing CLI commands. If the `locale` command is
present, do a best effort lookup to determine the properly value to use
for the LANG environment variable.
This commit is contained in:
Chris Roberts 2023-05-12 15:00:34 -07:00
parent 62c40e7395
commit d53d8e61bc
3 changed files with 149 additions and 1 deletions

View File

@ -462,7 +462,7 @@ module VagrantPlugins
# Append in the options for subprocess
# NOTE: We include the LANG env var set to C to prevent command output
# from being localized
command << { notify: [:stdout, :stderr], env: {LANG: "C"}}
command << { notify: [:stdout, :stderr], env: env_lang}
Vagrant::Util::Busy.busy(int_callback) do
Vagrant::Util::Subprocess.execute(@vboxmanage_path, *command, &block)
@ -471,6 +471,52 @@ module VagrantPlugins
raise Vagrant::Errors::VBoxManageLaunchError,
message: e.to_s
end
private
# List of LANG values to attempt to use
LANG_VARIATIONS = %w(C C.UTF-8 C.utf8 en_US.UTF-8 en_US.utf8 POSIX).map(&:freeze).freeze
# By default set the LANG to C. If the host has the locale command
# available, check installed locales and verify C is included (or
# use C variant if available).
def env_lang
# If already set, just return immediately
return @env_lang if @env_lang
# Default the LANG to C
@env_lang = {LANG: "C"}
# If the locale command is not available, return default
return @env_lang if !Vagrant::Util::Which.which("locale")
@logger.debug("validating LANG value for virtualbox cli commands")
# Get list of available locales on the system
result = Vagrant::Util::Subprocess.execute("locale", "-a")
# If the command results in an error, just log the error
# and return the default value
if result.exit_code != 0
@logger.warn("locale command failed (exit code: #{result.exit_code}): #{result.stderr}")
return @env_lang
end
available = result.stdout.lines.map(&:chomp).find_all { |l|
l == "C" || l == "POSIX" || l.start_with?("C.") || l.start_with?("en_US.")
}
@logger.debug("list of available C locales: #{available.inspect}")
# Attempt to find a valid LANG from locale list
lang = LANG_VARIATIONS.detect { |l| available.include?(l) }
if lang
@logger.debug("valid variation found for LANG value: #{lang}")
@env_lang[:LANG] = lang
end
@logger.debug("LANG value set: #{@env_lang[:LANG].inspect}")
@env_lang
end
end
end
end

View File

@ -0,0 +1,99 @@
require_relative "../base"
require Vagrant.source_root.join("plugins/providers/virtualbox/driver/base")
describe VagrantPlugins::ProviderVirtualBox::Driver::Base do
describe "#env_lang" do
context "when locale command is not available" do
before do
allow(Vagrant::Util::Which).to receive(:which).with("locale").and_return(false)
end
it "should return default value" do
expect(subject.send(:env_lang)).to eq({LANG: "C"})
end
end
context "when the locale command is available" do
let(:result) { Vagrant::Util::Subprocess::Result.new(exit_code, stdout, stderr) }
let(:stderr) { "" }
let(:stdout) { "C.default" }
let(:exit_code) { 0 }
before do
allow(Vagrant::Util::Which).to receive(:which).with("locale").and_return(true)
allow(Vagrant::Util::Subprocess).to receive(:execute).with("locale", "-a").and_return(result)
end
context "when locale command errors" do
let(:exit_code) { 1 }
it "should return default value" do
expect(subject.send(:env_lang)).to eq({LANG: "C"})
end
end
context "when locale command does not error" do
let(:exit_code) { 0 }
let(:base) { "de_AT.utf8\nde_BE.utf8\nde_CH.utf8\nde_DE.utf8\nde_IT.utf8\nde_LI.utf8\nde_LU.utf8\nen_AG\nen_AG.utf8\nen_AU.utf8\nen_BW.utf8\nen_CA.utf8\nen_DK.utf8\nen_GB.utf8\nen_HK.utf8\nen_IE.utf8\nen_IL\nen_IL.utf8\nen_IN\nen_IN.utf8\nen_NG\n" }
context "when stdout includes C" do
let(:stdout) { "#{base}C\n" }
it "should use C for the lang" do
expect(subject.send(:env_lang)).to eq({LANG: "C"})
end
end
context "when stdout does not include C" do
context "when stdout includes C.UTF-8" do
let(:stdout) { "#{base}C.UTF-8\n"}
it "should use C.UTF-8 for the lang" do
expect(subject.send(:env_lang)).to eq({LANG: "C.UTF-8"})
end
end
context "when stdout includes C.utf8" do
let(:stdout) { "#{base}C.utf8\n"}
it "should use C.utf8 for the lang" do
expect(subject.send(:env_lang)).to eq({LANG: "C.utf8"})
end
end
context "when stdout includes POSIX" do
let(:stdout) { "#{base}POSIX\n"}
it "should use POSIX for the lang" do
expect(subject.send(:env_lang)).to eq({LANG: "POSIX"})
end
end
context "when stdout includes en_US.UTF-8" do
let(:stdout) { "#{base}en_US.UTF-8\n"}
it "should use en_US.UTF-8 for the lang" do
expect(subject.send(:env_lang)).to eq({LANG: "en_US.UTF-8"})
end
end
context "when stdout includes en_US.utf8" do
let(:stdout) { "#{base}en_US.utf8\n"}
it "should use en_US.utf8 for the lang" do
expect(subject.send(:env_lang)).to eq({LANG: "en_US.utf8"})
end
end
end
context "when stdout does not include any variations" do
let(:stdout) { base }
it "should default to C" do
expect(subject.send(:env_lang)).to eq({LANG: "C"})
end
end
end
end
end
end

View File

@ -28,6 +28,9 @@ shared_context "virtualbox" do
allow(subprocess).to receive(:execute).
with("VBoxManage", "showvminfo", kind_of(String), kind_of(Hash)).
and_return(subprocess_result(exit_code: 0))
allow(Vagrant::Util::Which).to receive(:which).and_call_original
allow(Vagrant::Util::Which).to receive(:which).with("locale").and_return(false)
end
around do |example|