Вопрос или проблема
Я работаю с устаревшей системой, изначально написанной на C для 3b2 SVR3, позже на C++ для Sun/Solaris, а теперь в основном для Linux. У нас есть несколько пользователей, которым нужно, чтобы наши приложения работали на Windows Server, поэтому мы уделяли этому внимание последние 18 лет, используя MSys / MinGW, кросс-компилируя с наших устройств разработки на базе *nix. Сейчас мы пытаемся обновиться до MSys 2 и столкнулись с, по-видимому, неразрешимой проблемой, на которую не удалось найти ответ, несмотря на все поиски.
Наше приложение работает одновременно в нескольких часовых поясах. Мы не можем полагаться на статическую настройку часового пояса для корректной работы. Мы получаем определения часового пояса из базы данных IANA, установленных в виде файлов zoneinfo в традиционных местах, в зависимости от окружения. Мы обновляем zoneinfo для наших пользователей по мере необходимости.
На протяжении всего времени, сколько я помню (я работаю с этим почти 30 лет), было достаточно
putenv("TZ=<ваш-часовой-пояс>");
tzset();
чтобы методы, такие как mktime() и localtime(), предоставляли нужную нам информацию. Это перестало работать, как только мы перешли на MSys 2.
Кроме того, большинство нашего кода не зависит от начального часового пояса, но один метод настаивает на том, чтобы переменная TZ имела какое-то значение (любое значение, это не важно). На самом деле, не имеет значения, почему TZ необходимо установить – этот код выявляет другой симптом, который мы иначе могли бы не заметить так быстро: при входе в любую из наших программ переменная окружения TZ была удалена из окружения.
Конечная любопытность в том, что MSys 2 идет с программой date, которая, кажется, работает нормально с переменной окружения TZ, как и printenv и ls, что указывает на то, что то, что нам нужно сделать, можно сделать.
У нас есть короткая тестовая программа, которую мы использовали для демонстрации проблемы и, если повезет, доказа, что мы нашли решение:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int
main(int argc, const char *const *argv)
{
const char *tz = getenv("TZ");
const char *ptz = tz ? tz : "TZ не установлен";
printf("при входе: %s\n", ptz);
time_t now = time(NULL);
putenv("TZ=US/Pacific");
tzset();
printf("в %s сейчас %s", getenv("TZ"), ctime(&now));
putenv("TZ=US/Eastern");
tzset();
printf("в %s сейчас %s", getenv("TZ"), ctime(&now));
return 0;
}
Когда я запустил вышеприведенное на нашей Linux машине для разработки, я получил следующий вывод:
$ printenv TZ
US/Central
$ date "+%F %T %Z"
2024-08-15 10:20:55 CDT
$ ./msystest
при входе: US/Central
в US/Pacific сейчас Thu Aug 15 08:20:55 2024
в US/Eastern сейчас Thu Aug 15 11:20:55 2024
Под MSys 2, всего через несколько секунд, я получил следующее:
$ printenv TZ
US/Central
$ date "+%F %T %Z"
2024-08-15 10:20:57 CDT
$ ./msystest
при входе: TZ не установлен
в US/Pacific сейчас Thu Aug 15 16:20:57 2024
в US/Eastern сейчас Thu Aug 15 16:20:57 2024
Используя ldd под MSys 2, мы заметили, что для date и msystest используются разные DLL:
$ ldd /usr/bin/date
ntdll.dll => /c/Windows/SYSTEM32/ntdll.dll (0x7ff962d20000)
KERNEL32.DLL => /c/Windows/System32/KERNEL32.DLL (0x7ff961b30000)
KERNELBASE.dll => /c/Windows/System32/KERNELBASE.dll (0x7ff960340000)
msys-intl-8.dll => /usr/bin/msys-intl-8.dll (0x430b30000)
msys-2.0.dll => /usr/bin/msys-2.0.dll (0x180040000)
msys-iconv-2.dll => /usr/bin/msys-iconv-2.dll (0x5603f0000)
$ ldd msystest
ntdll.dll => /c/Windows/SYSTEM32/ntdll.dll (0x7ff962d20000)
KERNEL32.DLL => /c/Windows/System32/KERNEL32.DLL (0x7ff961b30000)
KERNELBASE.dll => /c/Windows/System32/KERNELBASE.dll (0x7ff960340000)
msvcrt.dll => /c/Windows/System32/msvcrt.dll (0x7ff962790000)
Так что наш вопрос: является ли нашей проблемой разница в DLL, и если да, как нам заставить DLL MSys быть встроенными в наши программы вместо msvcrt DLL?
Мы сделали много поисков в Интернете, чтобы попытаться решить это, но без особого успеха. Обычно мы получаем один из двух ответов:
- Windows не работает таким образом, поэтому вы не можете сделать то, что хотите, с MSys
- Вот как вы создаете DLL с помощью MinGW
Не нужно говорить, что ответы либо (1) неверны, либо (2) отвечают на неправильный вопрос. Любая помощь, которую кто-то может предоставить/направить, будет бесценна.
Это известная ошибка в MSys2. Она намеренно удаляет переменную окружения TZ
при вызове приложений, не относящихся к MSys2.
Заглянув в исходный код, это происходит в build_env()
, которая вызывается когда программа MSys вызывает программу, не относящуюся к MSys. В частности, программа MSys – это такая, к которой обращаются через путь, проходящий через монтирование Cygwin с флагом MOUNT_CYGWIN_EXEC
. В mount
командной строке это параметр cygexec
.
Поэтому я приведу несколько потенциальных обходных решений. Обратите внимание, что я не тестировал ни одно из них, я просто основываю свои выводы на чтении кода и другого материала.
- Если вы контролируете как код программы, который интересует переменную окружения
TZ
, так и программу, которая его вызывает, передайте необходимую информацию о часовом поясе в другую переменную, такую какTZ_YES_MSYS2_I_MEAN_IT
, и запуститеsetenv("TZ", getenv("TZ_YES_MSYS2_I_MEAN_IT"), 1); tzset();
(плюс проверка ошибок) в коде инициализации вашей программы. Обратите внимание, что если ваше приложение содержит несколько программ, это необходимо сделать в каждой программе, так как вызов другой программы с помощью функцийexec*
удалитTZ
. - Если вы контролируете программу, которая вызывает вашу программу, заставьте ее установить
TZ
через не-MSys2 программу. Например, вызовитеcmd /c "set TZ=… && myprogram"
. Имейте в виду, что заключение путей и аргументов командной строки в кавычки сложно с cmd. Это то, что в конечном итоге сделал LLVM (они сообщали о баге, который я упоминал выше). - Если ваша программа расположена в
c:\path\to\my\program.exe
, выполнитеmount -o cygexec c:\path\to\my /usr/my/bin
и вызовите вашу программу через/usr/my/bin/program.exe
. Это обойдет удалениеTZ
.
Мы решили проблему, но не так, как хотели.
Корень проблемы в том, что MSys 2 имеет 3 режима работы на 64 бита, которые не являются на 100% взаимозаменяемыми. Есть основная msys, msvcrt и ucrt. Режимы msvcrt и ucrt используют служебные DLL, предоставляемые Windows, которые, как отмечено в десятках других постов, с которыми я столкнулся при исследовании проблемы, “не работают с часовыми поясами таким образом”, а именно с использованием базы данных zoneinfo вместе с переменной окружения TZ для интерпретации времени в различных часовых поясах. Таким образом, это не ошибка, это функция. Основной режим msys использует свою собственную служебную DLL, которая использует базу данных zoneinfo и переменную окружения TZ так, как это привыкли делать пользователи *nix.
Наша проблема заключалась в том, что кросс-компилятор, доступный нам, строил только для msvcrt. Если бы у нас было немного больше времени, мы, вероятно, смогли бы выяснить правильный файл спецификаций и скопировать необходимые библиотеки с установленной машины MSys 2, чтобы заставить кросс-компилятор производить именно то, что мы хотели (мы были очень близки), но люди, контролировавшие календарь, оказали достаточно давления, чтобы заставить нас запустить наши автоматические сборки на машине MSys 2. Это (сборка) медленнее, но работает.
Ответ или решение
Ваше описание проблемы, связанной с переменной окружения TZ в среде MSys2, действительно поднимает несколько важных аспектов, связанных с различиями в режимах работы MSys2 и их влиянием на обработку временных зон. Приведем подробное объяснение проблемы и возможные пути ее решения.
Суть проблемы
Как вы уже заметили, переменная окружения TZ не сохраняется при вызове приложений, которые не являются частью MSys2. Причина этого заключается в том, что MSys2 использует различные режимы работы (msvcrt, ucrt и core msys), и только core msys корректно взаимодействует с библиотеками, работающими с временными зонами через zoneinfo и переменную TZ.
Когда вы запускаете приложение, скомпилированное в режиме msvcrt или ucrt, оно использует стандартные сервисные библиотеки Windows, которые не поддерживают работу с переменной TZ так, как это делается в Unix-подобных системах.
Возможные решения
-
Переключение на core msys: Если ваш процесс сборки может быть настроен для использования режима core msys, это обеспечит корректное использование переменной TZ. Вы должны убедиться, что ваша сборка настраивается на использование необходимых библиотек MSys2, которые работают с временными зонами.
-
Передача временной зоны через другую переменную: Если вы контролируете как саму программу, так и ее вызовы, вы можете передать информацию о временной зоне через другую переменную окружения, например,
TZ_YES_MSYS2_I_MEAN_IT
. Затем в коде вашей программы можно установить переменную TZ на основе этой переменной перед вызовом модуля, требующего TZ:putenv("TZ=US/Pacific"); tzset();
-
Использование командной оболочки Windows: Если ваша программа запускается из другой программы, попробуйте использовать командную оболочку Windows для установки переменной перед запуском:
cmd /c "set TZ=US/Pacific && myprogram"
Будьте осторожны с кавычками и экранированием при передаче аргументов.
-
Обход удалении переменной TZ: Можете попробовать изменить способ, которым запускаются ваши программы, используя монтирование MSys2, чтобы избежать этой проблемы. Например, выполните команду:
mount -o cygexec c:\path\to\my /usr/my/bin
после чего вызывайте вашу программу через
/usr/my/bin/program.exe
.
Заключение
Рекомендуется настроить вашу среду на использование режима core msys. Это позволит избежать проблем с TZ и обеспечит ожидаемое поведение программы при работе с временными зонами. Если это невозможно, используйте альтернативные способы передачи временной информации.
Надеюсь, эти рекомендации помогут вам в решении возникшей проблемы и в дальнейшем обеспечат корректную работу ваших приложений в среде MSys2. Если у вас возникнут дополнительные вопросы или потребуется более глубокое понимание конфигурации, не стесняйтесь обращаться за дополнительной помощью.