Пользователь
0,0
рейтинг
30 июля 2014 в 02:07

Разработка → Сброс PHP-кеша через SQL-запрос или из пушки по воробьям tutorial


Доброго времени суток. Столкнулся с такой вот задачей — через SQL запрос сбросить PHP-кеш. Проще говоря, удалить несколько файлов в определённом каталоге. На входе имеется:
  • СУБД – PostgreSQL 9.3
  • ОС – Linux (Fedora, но не суть важно)

В итоге, получилось решение в связке python + C + bash (всего понемногу). Немного не Unix-way, но может кому-то пригодится.

Обернём команду удаления PHP-кеша в bash-скрипт, подумав про его дальнейшее расширение новым функционалом. Вообще говоря, было бы неплохо передавать нужную команду в виде параметра. Вот этот скрипт:
#!/bin/bash

function __clear_cache() {
        rm /var/www/html/cache/*.php
}

FUNCS=()
FUNCS+=("__clear_cache")

function function_exists() {
        local e
        for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
        return 1
}

if function_exists "$1" "${FUNCS[@]}"; then
        eval "$1"
else
        echo "Function $1 does not exists"
        exit 1
fi

Тут мы объявляем массив, который будет содержать список функций (т.е команд) и, перед вызовом переданной команды (т.е функции), проверяем, есть ли она в списке. Иначе нехороший пользователь может передать что-то типа rm -rf ... в качестве параметра, что успешно выполнится в eval. Установим владельца для этого скрипта root (хотя и apache хватит, но думаем про расширяемость, не забывая про осторожность) и сделаем скрипт выполняемым:
chown root:root sysutils
chmod ugo+x sysutils

Выполнять этот скрипт из СУБД можно через C (долгий путь, так как необходимо заморачиваться с созданием расширения к PostgreSQL) или через неуправляемый скриптовый язык, коим является plpython. Но для начала задумаемся — скрипт из СУБД будет выполняется с правами пользователя postgres, а удалить кеш может только apache (как минимум). Но не беда, есть же такая штука, как SUID флаг. Только вот проблема в том, что в Linux нельзя установить SUID флаг для скриптов (подробнее — тут). Вернее, можно, но эффективный user ID всё равно будет таким же как и реальный. Попробуем обойти это ограничение, написав небольшую программу на C, в которой будет вызов нашего скрипта. Вот её код:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
        setuid(0);

        char command[255];

        if (argc == 2)
        {
                snprintf(command, 255, "/usr/local/bin/sysutils %s", argv[1]);
                system(command);
        }
        else
        {
                printf("USAGE: sysutils-core <command>\n");
        }

        return 0;
}

Сперва мы устанавливаем эффективный user ID, затем вызываем скрипт, передав ему параметр – требуемую команду. Скомпилируем программу и установим SUID-флаг:
gcc -o sysutils-core sysutils-core.c
chmod u+s sysutils-core

Проверяем:
su postgres
./sysutils-core clear_cache

Теперь перейдём к СУБД-части.

Установим расширение plpython3 (предварительно установленное в систему), выполнив SQL-команду в соответствующей БД:
CREATE EXTENSION plpython3u;

Или используя консоль:
createlang plpython3u -h localhost -U postgres testdb

Функция в СУБД, через которую будет сбрасываться кеш имеет вид:
CREATE OR REPLACE FUNCTION clear_cache ( )
RETURNS void AS
$BODY$
	import os
	os.system("/usr/local/bin/sysutils-core __clear_cache")
$BODY$
LANGUAGE plpython3u VOLATILE;

Тут просто вызов sysutils-core с параметром clear_cache. Проверяем:
SELECT clear_cache ( );

При необходимости, вызов функции может быть не только под postgres (именно этот пользователь может создавать функции на неуправляемых языках), в таком случае функции при создании необходимо указать опцию — SECURITY DEFINER (аналог SUID в СУБД).

Вот и всё. При желании, можно добавить передачу аргументов, новые команды да и вообще управлять linux-сервером через СУБД. Пусть это будет домашним заданием.

UPD: Более безопасный вариант программы на C (через execl):
Скрытый текст
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <error.h>
#include <sys/wait.h>
#include <errno.h>

int main(int argc, char *argv[])
{
	setuid(0);
	
	if (argc == 2)
	{
		int status = 0;
		int pid = fork();

		switch (pid)
		{
		case -1:
			printf("Fork sysutils process failed");
			exit(1);
		case 0:
			execl("/bin/bash", "bash", "/usr/local/bin/sysutils", argv[1], NULL);
		default:
			wait(&status);
		}
	}
	else
	{
		printf("USAGE: sysutils-core <command>\n");
	}

	return 0;
}

@blackmaster
карма
59,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

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

  • +1
    На превью статьи не хватает двух слонов :)

    Это just for fun или рабочая задача? Для каких целей используется очистка файлового кеша с помощю SQL?
    • +1
      Вероятно, всё же будет just for fun.
    • 0
      Готов предположить, что в БД есть логика (триггеры, процедуры), и вот там надо сбрасывать кеш. Была подобная ситуация, только с memcached из Oracle PL/SQL. Вполне себе реальная задача, а не just for fun.

      Косяк, конечно, в том, что создатель кеша (PHP-что-то-там) и инвалидатор кеша (PostgreSQL) находятся в разных слоях архитектуры, и тем размазывают ответственность за кеш (ну или данные — как посмотреть). Но это исправлять дольше и дороже.
  • +3
    Я тоже не могу себе представить, зачем такое может понадобится.
    Могу предложить лишь альтернативный вариант, не такой сложный в поддержке: postgres ставит у себя флаг того, что надо сбросить кеш (в крайнем случае в однострочной таблице). Это делается триггером либо через ту же процедуру clear_cache. В php приложении поднимается демон и периодически (пусть в 30 секунд) проверяется значение этого флага. Если флаг взведен — очистить кеш, сбросить флаг.
  • +3
    Да, я правильно понимаю, что setuid(0) — это переключение текущего юзера на рут?
    А если вдруг команда окажется более 254 символов?

    Я вижу огромное количество мест в текущем решении, где можно себе в будущем выстрелить в ногу. Так по-моему делать точно нельзя.
    • 0
      snprintf конечно же! Ох… серьезно напутал.
  • 0
    Мсье знает толк в извращениях (с)

    * Запустить /bin/rm напрямую?
    * Сделать кэш доступным для удаления из-под пользователя БД? Группы, права и прочее.
    • +1
      Более простым, но менее интересным, будет добавление postgres в группу, которая имеет право на запись в директорию (т.е. может удалять файлы).
  • +3
    Зачем? Вы реально этим пользуетесь?
  • 0
    А это есть где-то в продакшене?
    Хороший человек ведь всегда может сделать ./sysutils-core «clear_cache;rm -rf /»
    • 0
      В скрипте есть проверка на те команды (функции), которые может вызвать пользователь:
      if function_exists "$1" "${FUNCS[@]}"; then
      • +1
        а в system нет
        • 0
          Добавил более безопасный вариант (через execl).
  • 0
    Коммент про трамвай.
  • 0
    www.hagander.net/talks/
    Data driven cache invalidation (slightly updated), JDCon-East, New York City, NY, March 2011 and EuroPython 2011, Florence, Italy (+ scripts)

    че-то правда у него пдфка не грузится, а
    скрипты скачиваются

    вот еще видео
    ep2013.europython.eu/conference/talks/data-driven-cache-invalidation

    суть в PGQ

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.