Redis — это такое хранилище вида ключ-значение. Переменные окружения (environment variables) — напоминают то же самое. А что если это как-то объединить?
Для любителей пятничных постов, несложного хакинга и странных желаний — прошу под кат
Существует внушительный список клиентских библиотек для Redis на почти всех языках программирования. Но что, если:
- есть уже существующие приложения, изменять которые нехорошо;
- необходимо/хочется сделать приложение, умеющее работать как с Redis, так и без него;
- проще — лучше. Довольно часто работа с кэшом носит чисто вспомогательный характер и привносит излишнюю сложность в приложение.
В моем случае задача возникла после создание очередного CGI-like сервиса, которому необходимо было сохранять состояние. При этом выполнение этого скрипта может происходить на разных машинах.
Так как дело было примерно часа в 2 ночи и для меня уже наступила пятница, было принято решение расслабится и сделать что-нибудь несложное и интересное.
Вопрос и идея
Можно ли перехватить системные вызовы так, что при записи в переменные окружения (setenv
), данные
записывались в Redis, а при чтении (getenv
) наоборот доставались из кэша?
Схематично выглядит так:
Application <-- [syscall]--> [Wrapper] <-- [GET/DEL/...] --> [Redis]
Исследование
Это возможно?
Да, есть хорошая статья, где описывается как делать перехватчики системных вызовов.
Кто-то должен инициализировать подключение...
Есть малопопулярная возможность указать функции инициализации (constructor) и финализирования (destructor) в разделяемой библиотеки. В них и будем подключаться.
Реализация
Пришлось изучить спецификацию POSIX'a и Linux'a по этой теме.
Функции, которые необходимо было перехватить:
putenv
setenv
getenv
secure_getenv
clearenv
unsetenv
Зависимости
- C11
- hiredis 0.13
Сборка
Подвохов нет — типичный CMake с Github'a
Зависимости
sudo apt install libhiredis-dev cmake build-essential
Сборка
git clone https://github.com/reddec/envredis.git
cd envredis
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release ../ && make
Использование
Важный момент: некоторые приложения не меняют переменные окружения, а только внутренний массив. В таких приложениях данные из Redis будут получены, но обратно не отобразятся.
Хороший вариант — python. Согласно документации, изменение в os.environ
отображается в переменных окружения.
Допустим, уже поднят Redis на локальной машине.
- Выполним вывод переменной
XYZ
$ python -c 'import os; print("XYZ=" + os.environ.get("XYZ", ""))'
# вывод будет такой:
# XYZ=
- Зададим переменную в Redis
$ redis-cli SET XYZ "Hello world"
# вывод будет такой:
# OK
- Выполним вместе с перехватчиком
LD_PRELOAD=/path/to/libenvredis.so python -c 'import os; print("XYZ=" + os.environ.get("XYZ", ""))'
# вывод будет
# XYZ=Hello world
- Теперь попробуем задать переменную
LD_PRELOAD=/path/to/libenvredis.so python -c 'import os; os.environ["SAY"] = "Bye!"'
- Проверим, что она отобразилась в Redis'e
redis-cli GET SAY
# вывод будет
# "Bye!"
P.S.
Задача заняла примерно два часа, с учетом изучения предметной области. Сделано больше для фана, нежели для реальных целей.
Тем не менее кому-то может пригодится.