Безопасно ли перемещать добавляемый файл?

28

У меня есть процесс node.js, который использует fs.appendFileдля добавления строк file.log. Добавляются только полные строки по 40 символов в каждой строке, например, звонки похожи fs.appendFile("start-end"), а не 2 звонка, как fs.appendFile("start-")и fs.appendFile("end"). Если я переместу этот файл в, file2.logмогу ли я быть уверен, что строки не будут потеряны или скопированы частично?

пушистый
источник

Ответы:

36

Пока вы не перемещаете файл за границы файловой системы, операция должна быть безопасной. Это связано с механизмом того, как на самом деле выполняется «перемещение».

Если вы mvфайл в той же файловой системе, файл фактически не затрагивается, а изменяется только запись файловой системы.

$ mv foo bar

на самом деле делает что-то вроде

$ ln foo bar
$ rm foo

Это позволит создать жесткую ссылку (вторая запись каталога) для файла ( на самом деле индексный дескриптор указываемого записи файловой системы) с fooименем barи удалить fooзапись. Поскольку теперь при удалении fooсуществует вторая запись файловой системы, указывающая на fooinode, удаление старой записи fooфактически не удаляет блоки, принадлежащие inode.

Ваша программа с радостью добавит файл в любом случае, поскольку ее дескриптор открытого файла указывает на индекс файла, а не на запись файловой системы.

Примечание. Если ваша программа закроет и снова откроет файл между операциями записи, вы получите новый файл со старой записью файловой системы!

Перемещение кросс-файловой системы:

Если вы перемещаете файл за границы файловой системы, все становится ужасно. В этом случае вы не можете гарантировать согласованность вашего файла, так mvкак на самом деле

  • создать новый файл в целевой файловой системе
  • скопировать содержимое старого файла в новый файл
  • удалить старый файл

или

$ cp /path/to/foo /path/to/bar
$ rm /path/to/foo

соответственно

$ touch /path/to/bar
$ cat < /path/to/foo > /path/to/bar
$ rm /path/to/foo

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

Кроме того, если ваше приложение не закроет и не откроет старый файл, оно продолжит запись в старый файл, даже если он кажется удаленным: ядро ​​знает, какие файлы открыты, и хотя оно удалит запись файловой системы, оно не удалит инод старого файла и связанные с ним блоки, пока ваше приложение не закроет свой дескриптор открытого файла.

Андреас Визе
источник
3
К вашему сведению, ранние версии Unix не имели rename()системного вызова. Таким образом, оригинальная версия mvдействительно требовала link()создать жесткую ссылку, а затем unlink()удалить оригинальное имя. rename()был добавлен во FreeBSD, чтобы реализовать это атомарно в ядре.
Бармар
Извини но что это file-system borders?
laike9m
1
@ laike9m - границы файловой системы означают, что простая файловая система должна находиться в одном разделе на одном устройстве памяти, например на диске. Если вы переименуете файл в файловой системе, все, что изменится, это его имя в записи каталога. Он по-прежнему имеет тот же самый inode - если он был в файловой системе, основанной на inode, - как и большинство файловых систем Linux. Но если вы переместите файл в другую файловую систему, фактические данные должны быть перемещены, и файл получит новый inode из новой файловой системы. Это нарушит все операции с файлом, которые выполнялись, когда это произошло.
Джо
9

Поскольку вы говорите, что используете node.js, я предполагаю, что вы будете использовать fs.rename()(или fs.renameSync()) для переименования файлов. Этот метод node.js документирован для использования системного вызова rename (2) , который никоим образом не затрагивает сам файл, а просто меняет имя, под которым он указан в файловой системе:

« rename () переименовывает файл, перемещая его между каталогами, если это необходимо. Любые другие жесткие ссылки на файл (созданные с помощью link (2) ) не затрагиваются. Дескрипторы открытого файла для oldpath также не затрагиваются».

В частности, обратите внимание на последнее предложение, приведенное выше, в котором говорится, что любые дескрипторы открытого файла (например, ваша программа будет использовать для записи в файл) будут продолжать указывать на него даже после того, как он был переименован. Таким образом, не будет потери или повреждения данных, даже если файл будет переименован во время одновременной записи.


Как отмечает в своем ответе Андреас Вайз, системный вызов rename (2) (и, следовательно, fs.rename()в файле node.js) не будет работать через границы файловой системы. Таким образом, попытка переместить файл в другую файловую систему таким способом просто потерпит неудачу.

Команда Unix mvпытается скрыть это ограничение, обнаруживая ошибку и, вместо этого, перемещая файл, копируя его содержимое в новый файл и удаляя оригинал. К сожалению, перемещение таких файлов может привести к потере данных, если файл будет перемещен во время записи. Таким образом, если вы хотите безопасно переименовать файлы, в которые можно одновременно записывать, вы не должны их использовать mv(или, по крайней мере, вы должны быть абсолютно уверены, что новый и старый пути находятся в одной файловой системе).

Илмари Каронен
источник