ru en uk

  авторизация

(044) 362 48 16   (098) 294 41 60


   Цены

Home   |   WEB development   |   Articles   |   Programming in PHP

В PHP Manual о flock() написано следующее:


"Warning! On most operation systems flock() is implemented at the process level. When using a multithreaded server API like ISAPI you cannot rely on flock() to protect files against other PHP scripts running in parallel threads of the same server instance!"


То есть, буквально, если вы используете мультитредный (треды - они и есть треды ;) API, такой как ISAPI (например, IIS вместо Apache под Windows NT/2000), вы не можете использовать flock() для защиты файлов от других PHP скриптов, запущенных в параллельных тредах одного и того-же серверного процесса.
Вкратце, способ, о котором пойдет речь, выглядит так: перед тем, как открыть файл, доступный для записи, мы создаем соответствующий уникальный lock-файл, который удаляется после закрытия "основного" файла. Таким образом, другая копия процесса, перед открытием нашего файла, проверяет наличие соответствующего ему блокировочного "флага" и, если он присутствует, процесс ожидает, когда этот "флаг" будет удален, после чего сам создает такой файл-"флаг", блокируя доступ к рабочему файлу для других запущенных копий скрипта.

Желательно использовать блокировку не только при записи в файл, но и при чтении, так-как, теоретически, всегда есть возможность одновременного открытия файла на чтение и на запись разными копиями скрипта (в обоих случаях я говорю только о файлах, содержимое которых будет изменяться).
Обычно, запись в файл происходит одномоментно, при этом либо в конец дописывается строка/строки (как в гостевой книге), либо он обнуляется и записывается заново (счетчики, системы голосования), тоже можно сказать и про чтение, поэтому время задержки загрузки страницы, даже при очень большом количестве обращений к такому файлу, будет минимальным. Кстати, хотя речь идет о действительно малом промежутке времени, тем не менее, при работе с файлами больших размеров, чтобы свести к минимуму задержку, советую для чтения использовать только функцию file() (или fileToArray() - см. дальше), если, конечно, вам не нужно прочитать 1-2 строчки, а, в случае записи, создавать временный буфер, заполняя его по мере выполнения скрипта, и в конце сбрасывать его в рабочий файл. Эта общая практика, используемая не только в PHP, реально ускоряет работу, особенно, если каждая строка считываемого файла подвергается лексическому разбору (парсится) или каждая строка записываемого - генерируется отдельно.
Теперь, собственно, о блокировке. Для начала, выбираем каталог, в котором будут храниться lock-файлы. Можно, конечно, использовать текущий каталог, но тогда все такие каталоги придется сделать доступными для записи всем пользователям, что нежелательно и не очень удобно. Лучше всего создадим его вне директории с нашими HTML-документами. На UNIX-like машине (в дальнейшем это будет подразумеваться) это может быть $HOME/lock (telnet-имся и набираем в командной строке: mkdir $HOME/lock; chmod 0777 $HOME/lock) или, если доступа к домашнему каталогу нету, делаем его в корне WWW-директории с помощью FTP-клиента, не забывая поставить permissions 777 (rwxrwxrwx), и обязательно создаем в нем файл .htaccess, запрещая сюда доступ по HTTP, c такими строчками:

Order Deny,Allow
Deny from all


Создаем файл lib.php - тут будут храниться общие функции и переменные, и кидаем его, допустим, тоже в корень вашего WWW-проекта (тогда не лишним будет тут тоже создать файл .htaccess со строками:

Order Deny,Allow
Deny from all

чтобы запретить доступ к нашему файлу-библиотеке для браузеров. В lib.php пишем:

<?
$LOCK_DIR = "$HOME/lock";
/* или $LOCK_DIR = "[путь файловой системы к вашему www-корню]/lock"; */

/*
* Внутренняя функция, блокирующая файл, непосредственно мы к ней обращаться
не будем
*/
function safeLock($file) {
global $LOCK_DIR;
$lck = "$LOCK_DIR/".basename($file).".LCK";
while(1) {
if (is_file($lck)) continue;
return touch($lck);
}
}
/*
* Т.с., только для разблокирования
*/
function safeUlock($file) {
global $LOCK_DIR;
$lck = "$LOCK_DIR/".basename($file).".LCK";
return unlink($lck);
}

/*
* Безопасная замена встроенной функции file()
*/
function fileToArray($file) {
if (!is_readable($file)) return FALSE;
safeLock($file);
$buf = file($file);
safeUlock($file);
return $buf;
}

/*
* А эта - вместо системной fopen() - аргументы те-же самые
*/
function safeOpen($file, $mode) {
safeLock($file);
return fopen($file, $mode);
}

/*
* Замена системного fclose()
* !ВНИМАНИЕ! У этой функции 2 аргумента, в отличии от fclose():
* первый $fd - идентичен fclose();
* второй $file - строка с именем закрываемого файла,
* с путем или без - не важно.
*/
function safeClose($fd, $file) {
$st = fclose($fd);
safeUlock($file);
return $st;
}
?>


Теперь можно смело начинать использовать нашу альтернативную блокировку: вместо file() мы будем использовать fileToArray(), вместо fopen() - safeOpen(), а вместо fclose() - safeClose(), не забывая про второй аргумент с именем файла. И самое главное - подключить наш lib.php:

<?
include ("$DOCUMENT_ROOT/lib.php");
/*
* Если ваш адрес имеет вид http://www.someisp.com/~user5
* то вместо $DOCUMENT_ROOT надо прописать полный путь файловой системы
* к корню вашей страницы, н.п.:
"/usr/home/www-users/user5/.public_html/lib.php"
*/

/* ... */

$gbfile = "$HOME/wwwdata/guestbook.dat";

$guestBook = fileToArray($gbfile);

while (list(,$key)=each($guestBook)) {
/* ... */
}

/* ... */

$counter = "$HOME/wwwdata/counter.txt";

$fdCount = safeOpen($counter, 'w');

fwrite($fdCount, $someCounterData);

safeClose($fdCount, $counter);

/* ... */
?>


Недостатки


1. Как видно из кода, есть потенциальная возможность конфликта при одновременной блокировке двух разных файлов с одинаковыми именами, т.к. имя lock-файла "не совсем" уникально; в принципе, это легко решается добавлением к имени, скажем, даты создания файла-объекта, или MD5-суммы, генерируемой из строки его полного пути. Но, поскольку описанный случай возможен разве что в достаточно сложном многопользовательском проекте с общей lock-директорией, я не стал этого делать.
2. Опять-же, в теории, остается возможность для хакера вычислить место нахождения каталога для lock-файлов и их имена и, запустив на том-же сервере свой PHP-скрипт с соответствующим кодом, "залочить", вашу страницу на веки вечные, но, насколько я понимаю, это возможно только если в файле конфигурации PHP не прописано "safe_mode = On".

 
Основы безопасности
29.05.2007
Блокировка файлов
29.05.2007
Введение в PHP5
29.05.2007