diff --git a/src/linux/init/config.cpp b/src/linux/init/config.cpp index 3d3b59bfd..93a87a1d4 100644 --- a/src/linux/init/config.cpp +++ b/src/linux/init/config.cpp @@ -53,6 +53,7 @@ Module Name: #define HOSTS_FILE_PATH ETC_FOLDER "hosts" #define LANG_ENV "LANG" #define LOCALE_FILE_PATH ETC_DEFAULT_FOLDER "locale" +#define LOCALE_CONF_FILE_PATH ETC_FOLDER "locale.conf" #define PATH_ENV "PATH" #define RESOLV_CONF_DIRECTORY_MODE 0755 #define RESOLV_CONF_FILE_MODE 0644 @@ -2514,9 +2515,14 @@ void ConfigUpdateLanguage(EnvironmentBlock& Environment) Routine Description: - This routine queries the contents of the /etc/default/locale text file and + This routine queries the contents of the locale configuration file and if present updates the $LANG environment variable in the environment block. + Different distributions store this file in different locations: + - /etc/default/locale + - /etc/locale.conf + Both share the same "LANG=" line format, so the first file that exists is used. + Arguments: Environment - Supplies the environment block pointer to update. @@ -2530,22 +2536,33 @@ Return Value: try { // - // Attempt to open the /etc/default/locale file. If the file does not exist - // then the $LANG environment variable will not be updated. + // Attempt to open the locale configuration file, trying each known path in turn. + // If none of the files exist then the $LANG environment variable will not be updated. // - // N.B. This file is being opened by root. The only user-visible content + // N.B. These files are being opened by root. The only user-visible content // will be the contents of the last line of the file that contains // "LANG=". // - wil::unique_file LocaleFile{fopen(LOCALE_FILE_PATH, "r")}; - if (!LocaleFile) + constexpr const char* LocaleFilePaths[] = {LOCALE_FILE_PATH, LOCALE_CONF_FILE_PATH}; + + wil::unique_file LocaleFile; + for (const auto* Path : LocaleFilePaths) { + LocaleFile.reset(fopen(Path, "r")); + if (LocaleFile) + { + break; + } + if (errno != ENOENT) { - LOG_ERROR("fopen({}) failed {}", LOCALE_FILE_PATH, errno); + LOG_ERROR("fopen({}) failed {}", Path, errno); } + } + if (!LocaleFile) + { return; } diff --git a/test/windows/UnitTests.cpp b/test/windows/UnitTests.cpp index 1109b9687..5faca0933 100644 --- a/test/windows/UnitTests.cpp +++ b/test/windows/UnitTests.cpp @@ -516,6 +516,46 @@ class UnitTests } } + WSL2_TEST_METHOD(ConfigUpdateLanguage) + { + // Validates that init populates $LANG from the distro locale configuration file. + // ConfigUpdateLanguage reads /etc/default/locale first, then /etc/locale.conf, and uses + // the first file that exists. See ConfigUpdateLanguage in src/linux/init/config.cpp. + + DistroFileChange defaultLocale(L"/etc/default/locale", LxsstuLaunchWsl(L"test -f /etc/default/locale") == 0); + DistroFileChange localeConf(L"/etc/locale.conf", LxsstuLaunchWsl(L"test -f /etc/locale.conf") == 0); + + const auto readLang = []() { return LxsstuLaunchWslAndCaptureOutput(L"echo $LANG").first; }; + + // Only /etc/default/locale is present (Debian/Ubuntu). + { + defaultLocale.Delete(); + localeConf.Delete(); + defaultLocale.SetContent(L"LANG=de_DE.UTF-8\n"); + TerminateDistribution(); + VERIFY_ARE_EQUAL(readLang(), L"de_DE.UTF-8\n"); + } + + // Only /etc/locale.conf is present (Fedora, Arch, openSUSE, ...). + { + defaultLocale.Delete(); + localeConf.Delete(); + localeConf.SetContent(L"LANG=fr_FR.UTF-8\n"); + TerminateDistribution(); + VERIFY_ARE_EQUAL(readLang(), L"fr_FR.UTF-8\n"); + } + + // Both files are present: /etc/default/locale takes precedence because it is read first. + { + defaultLocale.Delete(); + localeConf.Delete(); + defaultLocale.SetContent(L"LANG=ja_JP.UTF-8\n"); + localeConf.SetContent(L"LANG=en_US.UTF-8\n"); + TerminateDistribution(); + VERIFY_ARE_EQUAL(readLang(), L"ja_JP.UTF-8\n"); + } + } + TEST_METHOD(Dup) { VERIFY_NO_THROW(LxsstuRunTest(L"/data/test/wsl_unit_tests dup", L"Dup"));