diff --git a/plugins/providers/virtualbox/driver/base.rb b/plugins/providers/virtualbox/driver/base.rb index 57602b483..7da23e23b 100644 --- a/plugins/providers/virtualbox/driver/base.rb +++ b/plugins/providers/virtualbox/driver/base.rb @@ -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 diff --git a/test/unit/plugins/providers/virtualbox/driver/base.rb b/test/unit/plugins/providers/virtualbox/driver/base.rb new file mode 100644 index 000000000..08c94283c --- /dev/null +++ b/test/unit/plugins/providers/virtualbox/driver/base.rb @@ -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 diff --git a/test/unit/support/shared/virtualbox_context.rb b/test/unit/support/shared/virtualbox_context.rb index 1909ccdc9..554467dc0 100644 --- a/test/unit/support/shared/virtualbox_context.rb +++ b/test/unit/support/shared/virtualbox_context.rb @@ -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|