Est-il sûr de déplacer un fichier qui est ajouté à?

28

J'ai un processus node.js qui utilise fs.appendFilepour ajouter des lignes file.log. Seules les lignes complètes d'environ 40 caractères par ligne sont ajoutées, par exemple, les appels sont similaires fs.appendFile("start-end"), pas 2 appels similaires fs.appendFile("start-")et fs.appendFile("end"). Si je déplace ce fichier vers, file2.logpuis-je être sûr qu'aucune ligne n'est perdue ou copiée partiellement?

Duveteux
la source

Réponses:

36

Tant que vous ne déplacez pas le fichier au-delà des frontières du système de fichiers, l'opération doit être sûre. Cela est dû au mécanisme, à la façon dont »le déplacement« se fait réellement.

Si vous mvun fichier sur le même système de fichiers, le fichier n'est pas réellement touché, mais seule l'entrée du système de fichiers est modifiée.

$ mv foo bar

fait quelque chose comme

$ ln foo bar
$ rm foo

Cela créerait un lien dur (une deuxième entrée de répertoire) pour le fichier (en fait l'inode pointé par l'entrée du système de fichiers) foonommé baret supprimer l' fooentrée. Depuis lors de la suppression foo, il existe une deuxième entrée du système de fichiers pointant vers fool'inode, la suppression de l'ancienne entrée foone supprime en fait aucun bloc appartenant à l'inode.

Votre programme se joindrait volontiers au fichier de toute façon, car son descripteur de fichier ouvert pointe vers l'inode du fichier, pas l'entrée du système de fichiers.

Remarque: Si votre programme ferme et rouvre le fichier entre les écritures, vous finirez par avoir un nouveau fichier créé avec l'ancienne entrée du système de fichiers!

Déplacements entre systèmes de fichiers:

Si vous déplacez le fichier au-delà des frontières du système de fichiers, les choses deviennent laides. Dans ce cas , vous ne pouviez pas garantir d'avoir votre dossier en gardant constante, depuis mvREELLEMENT

  • créer un nouveau fichier sur le système de fichiers cible
  • copier le contenu de l'ancien fichier dans le nouveau fichier
  • supprimer l'ancien fichier

ou

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

resp.

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

Selon que la copie atteint la fin du fichier lors de l'écriture de votre application, il peut arriver que vous ayez seulement la moitié d'une ligne dans le nouveau fichier.

De plus, si votre application ne ferme pas et ne rouvre pas l'ancien fichier, elle continuera à écrire dans l'ancien fichier, même s'il semble être supprimé: le noyau sait quels fichiers sont ouverts et bien qu'il supprime l'entrée du système de fichiers, il ne supprimera pas l'inode de l'ancien fichier et les blocs associés jusqu'à ce que votre application ferme son descripteur de fichier ouvert.

Andreas Wiese
la source
3
Pour info, les premières versions d'Unix n'avaient pas d' rename()appel système. Ainsi, la version originale de mveffectivement appelé link()pour créer le lien dur, suivie unlink()de supprimer le nom d'origine. rename()a été ajouté dans FreeBSD, pour l'implémenter atomiquement dans le noyau.
Barmar
Je suis désolé mais c'est quoi file-system borders?
laike9m du
1
@ laike9m - Les frontières du système de fichiers se réfèrent au fait qu'un système de fichiers simple doit résider sur une partition sur un périphérique de mémoire comme un lecteur de disque. Si vous renommez un fichier dans le système de fichiers, tout ce qui change, c'est son nom dans une entrée de répertoire. Il a toujours le même inode - s'il était dans un système de fichiers basé sur des inodes pour commencer - comme la plupart des systèmes de fichiers Linux. Mais, si vous déplacez le fichier vers un autre système de fichiers, les données réelles doivent être déplacées et le fichier recevra un nouvel inode du nouveau système de fichiers. Cela perturberait toutes les opérations sur le fichier qui étaient en cours lorsque cela s'est produit.
Joe
9

Puisque vous dites que vous utilisez node.js, je suppose que vous utiliseriez fs.rename()(ou fs.renameSync()) pour renommer les fichiers. Cette méthode node.js est documentée pour utiliser l' appel système rename (2) , qui ne touche en aucune façon le fichier lui-même, mais modifie simplement le nom sous lequel il est répertorié dans le système de fichiers:

" rename () renomme un fichier, le déplaçant entre les répertoires si nécessaire. Tous les autres liens durs vers le fichier (tels que créés à l'aide du lien (2) ) ne sont pas affectés. Les descripteurs de fichier ouverts pour oldpath ne sont pas non plus affectés."

En particulier, notez la dernière phrase citée ci-dessus, qui dit que tous les descripteurs de fichiers ouverts (tels que votre programme utiliseraient pour écrire dans le fichier) continueront à pointer vers lui même après qu'il a été renommé. Ainsi, il n'y aura aucune perte ou corruption de données même si le fichier est renommé pendant qu'il est simultanément écrit.


Comme le note Andreas Weise dans sa réponse , l'appel système rename (2) (et donc fs.rename()dans node.js) ne fonctionnera pas au-delà des limites du système de fichiers. Ainsi, tenter de déplacer un fichier vers un autre système de fichiers de cette manière échouera tout simplement.

La mvcommande Unix essaie de masquer cette limitation en détectant l'erreur et, à la place, en déplaçant le fichier en copiant son contenu dans un nouveau fichier et en supprimant l'original. Malheureusement, le déplacement de fichiers comme celui- ci risque de perdre des données si le fichier est déplacé pendant son écriture. Ainsi, si vous souhaitez renommer en toute sécurité des fichiers qui peuvent être écrits simultanément, vous ne devez pas utiliser mv(ou, au moins, vous devez vous assurer que le nouveau et l'ancien chemin se trouvent sur le même système de fichiers).

Ilmari Karonen
la source