Как `fopen` перед `rename` все еще читает старый контент с помощью PHP

Код ниже открывает файловый дескриптор ($fp) для определенного файла, затем я перезаписываю этот файл (используя rename), а затем читаю содержимое этого файла с помощью fread. Что меня поражает, так это то, что при чтении файла с помощью fread он по-прежнему указывает на содержимое исходного файла (до перезаписи)! Как это произошло? Я думаю, что это возможно только в том случае, если fopen сделает полную копию файла, чтобы его можно было прочитать позже, но я не могу поверить, что fopen делает полную копию файла, потому что это было бы очень неэффективно.

<?php

$fileA = "fileA.txt";
$fileB = "fileB.txt";

file_put_contents($fileA,"aaaaaaaaaaaaaa111111",LOCK_EX);
file_put_contents($fileB,"bbbbbbbbbbbbbb222222",LOCK_EX);

$fp = fopen($fileA,"r");

rename($fileB,$fileA);

echo fread($fp,10000);

?>

Удивительно, но приведенный выше код выводит aaaaaaaaaaaaaa111111, но должен выводить bbbbbbbbbbbbbb222222. Если раньше fread я использую fclose и снова открываю файл, он работает как положено (отображается новый контент).

Мой вопрос в том, как PHP все еще может отображать содержимое исходного файла, который уже был перезаписан! Не могу поверить, что PHP сделал полную копию файла, когда я позвонил fopen.

Приведенный выше код отлично работает в Linux, однако в Windows он не будет работать, поскольку использование rename в файле, уже открытом с помощью fopen, приведет к ошибке - в Linux все в порядке, ошибок нет.

Файл открыт, функция rename() не удалась. Вы не проверили возвращаемое значение.

Honk der Hase 15.07.2024 09:18

fopen вернет поток, который будет читать содержимое файла. При переименовании файла место, где находится содержимое, не меняется. Файловая система Linux работает следующим образом: каждый файловый блок по существу указывает на то, где находится следующий блок (я упрощаю), поэтому, как только у вас будет открыт поток в начале файла, вы сможете прочитать весь файл.

apokryfos 15.07.2024 09:36

@KenLee php.net/manual/en/function.rename.php говорит: «Если переименовать файл и он существует, он будет перезаписан».

brombeer 15.07.2024 10:36

@brombeer Они имеют в виду, что имя файла будет заменено ссылкой на переименовываемый файл, а не то, что содержимое файла будет скопировано поверх него. Я признаю, что это неудачно сформулировано.

Barmar 15.07.2024 17:51

@Barmar Это ответ на (теперь удаленный) комментарий Кенли, в котором он спрашивает что-то вроде: «Если файл A уже существует, можете ли вы даже переименовать файл B в файл A»

brombeer 15.07.2024 18:14

@HonkderHase Мне не нужно проверять значение, потому что значение действительно было переименовано, это не ошибка. Кроме того, меня беспокоит то, что для очень конфиденциального файла, когда я использую rename, я ожидаю, что его содержимое «уйдет», однако по какой-то причине PHP сохраняет старое содержимое файла очень живым и доступным. Я понимаю, что некоторые из вас сказали здесь о том, что ссылка не удаляется, поэтому, если у меня есть ссылка на файл, я могу продолжать обращаться к нему в будущем, даже если файл был переименован ранее?

Samul 15.07.2024 18:53

Для меня это звучит немного странно. Если вы переименуете файл, его содержимое не исчезнет. Любой, кто знает новое имя (или местонахождение данных на диске), по-прежнему сможет получить к ним доступ. Когда вы переименовываете файл, нет «старого файла» и «нового файла», это все тот же файл. В общих чертах, файловая система имеет своего рода «базу данных» (опять же, упрощая) имен файлов и указателей на то, где находятся (или начинаются) их данные. Когда вы переименовываете файл, вы просто меняете имя файла в этой базе данных, больше ничего не меняется. Все данные по-прежнему находятся в том же месте на диске.

apokryfos 16.07.2024 11:06
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
1
7
105
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Обратите внимание, что код (для вашего случая) в некоторой степени зависит от ОС.

Например, если мы немного изменим оператор переименования, чтобы отобразить результат функции переименования (например, как предложил комментатор):

echo "checking result of rename:" . rename($fileB,$fileA);

создание кода, чтобы стать:

<?php

$fileA = "fileA.txt";
$fileB = "fileB.txt";

file_put_contents($fileA,"aaaaaaaaaaaaaa111111",LOCK_EX);
file_put_contents($fileB,"bbbbbbbbbbbbbb222222",LOCK_EX);


$fp = fopen($fileA,"r");

echo "checking result of rename:" . rename($fileB,$fileA);
echo "<br>";


echo fread($fp,10000);

?>

тогда при запуске PHP-скрипта в ОС Windows результат (скажем, при запуске на XAMPP) будет:

Для того же кода, если запустить тот же PHP-скрипт в ОС Linux, результат будет:

Таким образом, для сценария PHP, работающего в ОС Win, поскольку ОС запрещает операцию переименования (когда файл «открыт»), операция перезаписи файла не выполняется при запуске функции переименования PHP, и, следовательно, система точно отображает содержимое исходный файл A.txt, который имеет вид «aaaaaaaaaaaaa111111». (да, я проверил, операция переименования не удалась, и там все еще есть два файла TXT)

Для сценария PHP, работающего в ОС Linux, операция переименования завершится успешно (даже если файл A.txt «открыт» с помощью fopen), поэтому PHP перезаписывает исходный файл A.txt содержимым файла B.txt с данными «bbbbbbbbbbbb222222». ".

Однако то, что вы наблюдаете, по-прежнему верно, потому что даже для PHP, работающего в ОС Linux, система будет отображать «aaaaaaaaaaaaaaaaaaaa111111», когда файл A.txt уже содержит данные «bbbbbbbbbbbbbb222222» после операции переименования.

Я проверил - это не имеет никакого отношения к тому, есть ли какая-либо задержка ввода-вывода в операции переименования перед операцией fread. Таким образом, даже если я добавлю строку «sleep(10)» перед оператором fread, система все равно будет отображать данные «aaaaaaaaaaaaaa111111», даже файл fileA.txt исчезнет и будет заменен на fileB.txt.

Таким образом, в этом случае дескриптор файла (в Linux) по-прежнему будет указывать на исходные данные файла, даже если вы используете функцию переименования для «перезаписи» данных исходного файла. Операция «перезаписи» (как указано в официальной документации PHP) сообщает вам результат операции. НО на самом деле дескриптор файла по-прежнему указывает на исходный поток данных (на вводе-выводе), поэтому система по-прежнему отображает старые данные.

Конечно, одним из способов решения проблемы будет обновление дескриптора файла, например:

  1. Закройте дескриптор файла после оператора переименования
  2. Повторно откройте файл A через fopen, чтобы получить новый дескриптор файла.

Итак, для PHP, работающего в ОС Linux, подойдет следующее: (отобразится «bbbbbbbbbbbbbb222222»)

<?php

$fileA = "fileA.txt";
$fileB = "fileB.txt";

file_put_contents($fileA,"aaaaaaaaaaaaaa111111",LOCK_EX);
file_put_contents($fileB,"bbbbbbbbbbbbbb222222",LOCK_EX);


$fp = fopen($fileA,"r");

echo "checking result of rename:" . rename($fileB,$fileA);
echo "<br>";

fclose($fp);
$fp = fopen($fileA,"r");

echo fread($fp,10000);

?>

Это довольно хорошее расследование, и я могу подтвердить оба варианта поведения, о которых вы сообщили: в Windows операция переименования завершается неудачно, но в Linux она завершается успешно. Кроме того, ваше предложение закрыть обработчик перед чтением файла заставляет все работать так, как ожидалось. Меня забеспокоило это странное поведение, потому что, когда я использую rename для переименования файла с очень конфиденциальной информацией, я очень надеюсь, что он исчез навсегда! Но, как вы доказали, если у другого процесса открыт этот старый файл (с помощью fopen), этот процесс может получить доступ ко всей конфиденциальной информации из старого файла, пока обработчик остается открытым. Опасный

Samul 17.07.2024 06:46

Другие вопросы по теме