Изменение текущей рабочей директории при перемещении скрипта

Goalni

Новичок
Здравствуйте.
У меня есть деплой скрипт, который вытягивает проект из системы контроля версии, заливает на продакшен и развёртывает его там.
Развёртывание заключается в простой замене папок со скриптами командой mv.
Код:
mv /var/www/project /var/deploy/project_old
mv /var/deploy/project_new /var/www/project
Проблема в том, что замена папок влияет на текущую рабочую директорию скриптов, которые работали в момент замены.

Ситуация воспроизводима:
PHP:
<?php
function go() {
    var_dump(getcwd(), file_exists('hello'));
}
go();
sleep(10);
go();
Если во время вызова sleep() переместить папку со скриптом в другое место, то вывод будет следующим:
Код:
string(16) "/home/lalala/bbb"
bool(true)
string(8) "/tmp/ccc"
bool(false)
Причём если перемещать сам скрипт, то смена рабочей директории не происходит.

Это поведение контринтуитивно и приводит к ошибкам непосредственно в момент деплоя. Проект повсеместно использует относительные пути и смена рабочей директории делает эти пути бессмысленными и входящими в конфликт с open_basedir.

Я не нашел в документации описание подобной багофичи. Может быть кто-то сталкивался с подобным?
Версия PHP 5.5.17. Смена cwd подтверждена в cli и fpm окружениях.
 

Goalni

Новичок
Не приведёте конкретную цитату из этого стандарта, объясняющую момент?
 

MiksIr

miksir@home:~$
Так а файл hello переезжает? Тут может быть в этом проблема: http://php.net/manual/ru/function.clearstatcache.php - полный путь к hello закешировался. Хотя у меня оба true получились. Как вариант попробуйте переключать деплой символьной ссылкой. Но самый правильный вариант - переключение на уровне конфига веб-сервера.
 

Goalni

Новичок
Файл hello лежит уровнем выше и не перемещается. То есть правильно было в примере написать file_exists('../hello').
 

MiksIr

miksir@home:~$
mv /var/www/project /var/www/project_old
mv/var/www/project_new /var/www/project
Решит вашу проблему. Поведение getcwd очевидно если понимать, как работает файловая система. По сути при перемещении в пределах одной фс вы остаетесь в той же директории, но к ней меняется путь (родитель у нее меняется). getcwd дает актуальный путь. Так что все работает как ожидается.
Но крайне советую переделать на изменение пути в конфиге веб-сервера и его мягком рестарте.
 

AnrDaemon

Продвинутый новичок
@MiksIr, так он же именно это и делает.
А символьные ссылки таки не очень хорошо воспринимаются веб-серверами в некоторых конфигурациях.
 

MiksIr

miksir@home:~$
@MiksIr, так он же именно это и делает.
Нет, он не так делает, он совершенно в другую директорию переносит - /var/deploy/, а потом жалуется, что у него ../hello не работает. По-этому все, что ему нужно - деплоить в той же директории, где основной сайт, т.е. /var/www.
 

Goalni

Новичок
После прочтения скрипта и его компиляции (или загрузки опкода из кеша), уже имеется вся информация для исполнения. Зачем РНР держится за файл скрипта на всём протяжении его исполнения? И почему проблема возникает только если переместить директорию со скриптом? При перемещении самого скрипта такого спецэффекта не наблюдается.

Понятно, что деплой надо изменить. Это я уже сделал в первую очередь. Охота разобраться с исходным вопросом до конца.
 

MiksIr

miksir@home:~$
Он не держится за файл. При запуске скрипта запоминается текущая директория. Директории в линуксе запоминаются не по пути, как вы думаете, а по inode. Когда вы просите текущий путь - рассчитывается полный путь к этой inode. При перемещении директории ее inode не меняется. А вот путь к inode меняется. По-этому второй запрос getcwd и дает другой путь. К слову, если вы переместите директорию вообще на другую файловую систему - то getcwd даст false. Ибо запомненной inode больше не существует в дереве.

Когда вы перемещаете файл - запомненная inode директории не меняется и остается в дереве точно там же, где и была. По-этому getcwd и даст ту же директорию.
 

AnrDaemon

Продвинутый новичок
Блин, я какую-то фигню написал. :) Спать больше надо.
 

Goalni

Новичок
Ок, что происходит на уровне ФС понятно.

Вот ещё один примечательный результат:
PHP:
<?php
function go() {
    $file = __DIR__."/../../var/blah";
    var_dump(getcwd(), $file, file_exists($file));
}

go();
sleep(20);
go();
Код:
vasya@ab ~ $ php /tmp/aaa/a.php
string(8) "/home/vasya"
string(23) "/tmp/aaa/../../var/blah"
bool(true)
string(8) "/home/vasya"
string(23) "/tmp/aaa/../../var/blah"
bool(false)
Во время sleep() исполняется команда mv /tmp/aaa /home/vasya/bbb
Как объяснить результат bool(false) во втором вызове go()?
 

Goalni

Новичок
false - результат вызова file_exists(), а не getcwd().
А вообще, у меня всё расположено в одной ФС (кроме /sys, /dev, /proc, /boot и /run).
 

MiksIr

miksir@home:~$
__DIR__ дает именно путь к скрипту (строку) и в процессе работы скрипта не меняется.
 

MiksIr

miksir@home:~$
Он идет по пути и проверяет каждый элемент. Так /tmp/aaa больше нет - на этом и останавливается.
Что бы избежать это - используйте функцию realpath и вынесите этот realpath(__DIR__...) из вашей функции, что бы оно выполнялось единожды.
 

Goalni

Новичок
Всё, понял. Это было довольно очевидно, не разглядел сразу.

Возвращаясь к исходному вопросу.
Похоже, PHP держит дескриптор файла со скриптом только для расчёта относительных путей. Это вполне логично, учитывая что в одном PHP процессе могут исполняться сотни скриптов и рабочую директорию текущего процесса уже использовать нельзя. Это единственное, ради чего PHP держит открытым файл со скриптом на всё время его исполнения? Или есть ещё кейсы, для которых нужен дескриптор файла со скриптом?
 

MiksIr

miksir@home:~$
PHP ничего не держит открытым. У каждого процесса в операционной системе есть понятие "текущая директория". Она используется _операционной системой_ для вычисления относительных путей. Т.е. этот механизм вообще к PHP отношения не имеет.
Когда в запускаете cgi или cli интерпретатор PHP - он просто наследует текущую директорию своего родителя, например, командной оболочки bash.
Все эти getcwd - это запросы в операционную систему. Системные вызовы.
 
Сверху