Ist es sicher, eine angehängte Datei zu verschieben?

28

Ich habe einen node.js-Prozess, mit fs.appendFiledem Zeilen hinzugefügt werden file.log. Nur komplette Linien von ca. 40 Zeichen pro Zeile angehängt werden , zB Anrufe sind wie fs.appendFile("start-end"), nicht zwei Anrufe wie fs.appendFile("start-")und fs.appendFile("end"). Wenn ich diese Datei verschiebe, file2.logkann ich dann sicher sein, dass keine Zeilen verloren gehen oder teilweise kopiert werden?

Flauschige
quelle

Antworten:

36

Solange Sie die Datei nicht über Dateisystemgrenzen hinweg verschieben, sollte der Vorgang sicher sein. Dies ist auf den Mechanismus zurückzuführen, wie »Bewegen« tatsächlich durchgeführt wird.

Wenn Sie mveine Datei auf demselben Dateisystem haben, wird die Datei nicht wirklich berührt, sondern nur der Dateisystemeintrag wird geändert.

$ mv foo bar

macht eigentlich sowas

$ ln foo bar
$ rm foo

Dies würde einen schaffen harten Link (ein zweiter Verzeichniseintrag) für die Datei (eigentlich die Inode wies nach Dateisystemeintrag) foogenannt barund das Entfernen fooEintrag. Da es jetzt beim Entfernen fooeinen zweiten Dateisystemeintrag gibt, der auf fooden Inode verweist , werden durch das Entfernen des alten Eintrags footatsächlich keine Blöcke entfernt, die zum Inode gehören.

Ihr Programm würde die Datei trotzdem gerne anhängen, da sein offenes Dateihandle auf den Inode der Datei und nicht auf den Dateisystemeintrag verweist.

Hinweis: Wenn Ihr Programm die Datei zwischen den Schreibvorgängen schließt und erneut öffnet, wird am Ende eine neue Datei mit dem alten Dateisystemeintrag erstellt!

Dateisystemübergreifende Verschiebungen:

Wenn Sie die Datei über Dateisystemgrenzen hinweg verschieben, werden die Dinge hässlich. In diesem Fall können Sie nicht garantieren, dass Ihre Datei konsistent bleibt, da mvdies tatsächlich der Fall wäre

  • Erstellen Sie eine neue Datei auf dem Zieldateisystem
  • Kopieren Sie den Inhalt der alten Datei in die neue Datei
  • entferne die alte Datei

oder

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

bzw.

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

Je nachdem, ob der Kopiervorgang während des Schreibens Ihrer Anwendung das Dateiende erreicht, kann es vorkommen, dass die neue Datei nur eine halbe Zeile enthält.

Wenn Ihre Anwendung die alte Datei nicht schließt und erneut öffnet, wird weiterhin in die alte Datei geschrieben, auch wenn sie gelöscht zu sein scheint: Der Kernel weiß, welche Dateien geöffnet sind, und obwohl der Dateisystemeintrag gelöscht wird, wird er gelöscht Löscht den Inode und die zugehörigen Blöcke der alten Datei erst, wenn die Anwendung das geöffnete Datei-Handle schließt.

Andreas Wiese
quelle
3
Zu Ihrer Information, frühe Versionen von Unix hatten keinen rename()Systemaufruf. Die ursprüngliche Version von hat also mvtatsächlich aufgerufen link(), um den festen Link zu erstellen, und anschließend unlink()den ursprünglichen Namen zu entfernen. rename()wurde in FreeBSD hinzugefügt, um dies atomar im Kernel zu implementieren.
Barmar
Es tut mir leid , aber was ist file-system borders?
laike9m
1
@ laike9m - Dateisystemgrenzen beziehen sich auf die Tatsache, dass sich ein einfaches Dateisystem wie ein Festplattenlaufwerk auf einer Partition auf einem Speichergerät befinden muss. Wenn Sie eine Datei innerhalb des Dateisystems umbenennen, ändert sich nur der Name in einem Verzeichniseintrag. Es hat immer noch den gleichen Inode - wenn es sich um ein Dateisystem handelt, das zunächst auf Inodes basiert - wie die meisten Linux-Dateisysteme. Wenn Sie die Datei jedoch in ein anderes Dateisystem verschieben, müssen die eigentlichen Daten verschoben werden, und die Datei erhält einen neuen Inode aus dem neuen Dateisystem. Dies würde alle Operationen an der Datei stören, die zu diesem Zeitpunkt ausgeführt wurden.
Joe
9

Da Sie sagen, dass Sie node.js verwenden, gehe ich davon aus, dass Sie fs.rename()(oder fs.renameSync()) verwenden, um die Dateien umzubenennen. Es ist dokumentiert, dass diese node.js-Methode den Systemaufruf rename (2) verwendet, der die Datei selbst in keiner Weise berührt, sondern lediglich den Namen ändert, unter dem sie im Dateisystem aufgeführt ist:

" rename () benennt eine Datei um und verschiebt sie bei Bedarf zwischen Verzeichnissen. Alle anderen festen Links zu der Datei (wie mit link (2) erstellt ) sind nicht betroffen. Offene Dateideskriptoren für oldpath sind ebenfalls nicht betroffen."

Beachten Sie insbesondere den zuletzt genannten Satz, der besagt, dass alle geöffneten Dateideskriptoren (wie sie Ihr Programm zum Schreiben in die Datei verwenden würde) auch nach dem Umbenennen weiterhin darauf verweisen. Auf diese Weise gehen auch dann keine Daten verloren oder werden beschädigt, wenn die Datei umbenannt wird, während gleichzeitig darauf geschrieben wird.


Wie Andreas Weise in seiner Antwort festhält , funktioniert der Systemaufruf rename (2) (und damit fs.rename()in node.js) nicht über Dateisystemgrenzen hinweg. Daher schlägt der Versuch, eine Datei auf diese Weise in ein anderes Dateisystem zu verschieben, einfach fehl.

Der Unix- mvBefehl versucht, diese Einschränkung auszublenden, indem er den Fehler erkennt und stattdessen die Datei verschiebt, indem er ihren Inhalt in eine neue Datei kopiert und das Original löscht. Leider besteht beim Verschieben von Dateien die Gefahr eines Datenverlusts, wenn die Datei während des Schreibens verschoben wird. Wenn Sie also Dateien, in die möglicherweise gleichzeitig geschrieben wird, sicher umbenennen möchten, sollten Sie nicht verwenden mv(oder zumindest sicherstellen, dass sich der neue und der alte Pfad auf demselben Dateisystem befinden).

Ilmari Karonen
quelle