Redis — это такое хранилище вида ключ-значение. Переменные окружения (environment variables) — напоминают то же самое. А что если это как-то объединить?


Для любителей пятничных постов, несложного хакинга и странных желаний — прошу под кат


Существует внушительный список клиентских библиотек для Redis на почти всех языках программирования. Но что, если:


  1. есть уже существующие приложения, изменять которые нехорошо;
  2. необходимо/хочется сделать приложение, умеющее работать как с Redis, так и без него;
  3. проще — лучше. Довольно часто работа с кэшом носит чисто вспомогательный характер и привносит излишнюю сложность в приложение.

В моем случае задача возникла после создание очередного 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.


Задача заняла примерно два часа, с учетом изучения предметной области. Сделано больше для фана, нежели для реальных целей.


Тем не менее кому-то может пригодится.


Комментарии (0)