Je souhaite copier un fichier de A vers B, qui peut se trouver sur différents systèmes de fichiers.
Il existe des exigences supplémentaires:
- La copie est tout ou rien, aucun fichier B partiel ou corrompu laissé en place lors d'un crash;
- N'écrasez pas un fichier B existant;
- Ne rivalisez pas avec une exécution simultanée de la même commande, tout au plus peut réussir.
Je pense que cela se rapproche:
cp A B.part && \
ln B B.part && \
rm B.part
Mais 3. est violé par le cp n'échouant pas si B.part existe (même avec le drapeau -n). Par la suite, 1. pourrait échouer si l'autre processus «gagne» le cp et que le fichier lié en place est incomplet. B.part pourrait également être un fichier non lié, mais je suis heureux d'échouer sans essayer d'autres noms cachés dans ce cas.
Je pense que bash noclobber aide, cela fonctionne-t-il pleinement? Existe-t-il un moyen de se passer de la version bash requise?
#!/usr/bin/env bash
set -o noclobber
cat A > B.part && \
ln B.part B && \
rm B.part
Suivi, je sais que certains systèmes de fichiers échoueront de toute façon (NFS). Existe-t-il un moyen de détecter de tels systèmes de fichiers?
Quelques autres questions liées mais pas tout à fait les mêmes:
Approximation du mouvement atomique à travers les systèmes de fichiers?
Le mv est-il atomique sur mon fs?
https://rcrowley.org/2010/01/06/things-unix-can-do-atomically.html
mv
écrasera un fichier existant B.mv -n
ne notifiera pas qu'il a échoué.ln(1)
(rename(2)
) échouera si B existe déjà.Réponses:
rsync
fait ce travail. Un fichier temporaire estO_EXCL
créé par défaut (désactivé uniquement si vous utilisez--inplace
), puisrenamed
sur le fichier cible. Utilisez--ignore-existing
pour ne pas écraser B s'il existe.En pratique, je n'ai jamais rencontré de problème avec cela sur des supports ext4, zfs ou même NFS.
la source
Ne vous inquiétez pas,
noclobber
c'est une fonctionnalité standard .la source
Vous avez posé des questions sur NFS. Ce type de code risque de casser sous NFS, car la vérification de
noclobber
implique deux opérations NFS distinctes (vérifier si le fichier existe, créer un nouveau fichier) et deux processus de deux clients NFS distincts peuvent entrer dans une condition de concurrence critique où les deux réussissent ( les deux vérifient qu'ilB.part
n'existe pas encore, puis les deux procèdent à sa création, ils se remplacent donc.)Il n'y a pas vraiment de vérification générique pour savoir si le système de fichiers sur lequel vous écrivez prendra en charge quelque chose comme
noclobber
atomiquement ou non. Vous pouvez vérifier le type de système de fichiers, que ce soit NFS, mais ce serait une heuristique et pas nécessairement une garantie. Les systèmes de fichiers comme SMB / CIFS (Samba) sont susceptibles de souffrir des mêmes problèmes. Les systèmes de fichiers exposés via FUSE peuvent ou non se comporter correctement, mais cela dépend principalement de l'implémentation.Une approche peut-être meilleure consiste à éviter la collision à l'
B.part
étape, en utilisant un nom de fichier unique (grâce à la coopération avec d'autres agents) afin que vous n'ayez pas à dépendre denoclobber
. Par exemple, vous pouvez inclure, dans le cadre du nom de fichier, votre nom d'hôte, votre PID et un horodatage (+ éventuellement un nombre aléatoire). garantir l'unicité.Donc, soit l'un des:
Ou:
Donc, si vous avez une condition de concurrence entre deux agents, ils procéderont tous les deux à l'opération, mais la dernière opération sera atomique, donc soit B existe avec une copie complète de A, soit B n'existe pas.
Vous pouvez réduire la taille de la course en vérifiant à nouveau après la copie et avant le
mv
ouln
opération , mais il y a encore une petite condition de course. Mais, quelle que soit la condition de concurrence, le contenu de B doit être cohérent, en supposant que les deux processus tentent de le créer à partir de A (ou d'une copie d'un fichier valide comme origine.)Notez que dans la première situation avec
mv
, lorsqu'une course existe, le dernier processus est celui qui gagne, car rename (2) remplacera atomiquement un fichier existant:Il est donc tout à fait possible que les processus consommant B à l'époque puissent en voir différentes versions (différents inodes) au cours de ce processus. Si les rédacteurs essaient tous de copier le même contenu et que les lecteurs consomment simplement le contenu du fichier, cela pourrait être bien, s'ils obtiennent des inodes différents pour des fichiers avec le même contenu, ils seront tout de même satisfaits.
La deuxième approche utilisant un lien dur semble meilleure, mais je me souviens avoir fait des expériences avec des liens durs dans une boucle étroite sur NFS à partir de nombreux clients simultanés et compter le succès et il semblait toujours y avoir des conditions de concurrence là-bas, où il semblait que deux clients avaient émis un lien dur opération en même temps, avec la même destination, les deux semblaient réussir. (Il est possible que ce comportement soit lié à l'implémentation particulière du serveur NFS, YMMV.) Dans tous les cas, il s'agit probablement du même type de condition de concurrence, où vous pourriez finir par obtenir deux inodes distincts pour le même fichier dans les cas où il y a du lourd concurrence entre les écrivains pour déclencher ces conditions de concurrence. Si vos rédacteurs sont cohérents (copiant tous les deux de A à B) et que vos lecteurs n'en consomment que le contenu, cela pourrait suffire.
Enfin, vous avez mentionné le verrouillage. Malheureusement, le verrouillage fait gravement défaut, au moins dans NFSv3 (je ne suis pas sûr de NFSv4, mais je parierais que ce n'est pas bon non plus.) Si vous envisagez de verrouiller, vous devriez examiner différents protocoles pour le verrouillage distribué, éventuellement hors bande avec le les copies de fichiers réelles, mais qui sont à la fois perturbatrices, complexes et sujettes à des problèmes tels que les blocages, donc je dirais qu'il vaut mieux être évité.
Pour plus d'informations sur le sujet de l'atomicité sur NFS, vous voudrez peut-être lire sur le format de boîte aux lettres Maildir , qui a été créé pour éviter les verrous et fonctionner de manière fiable même sur NFS. Il le fait en gardant des noms de fichiers uniques partout (de sorte que vous n'obtenez même pas un B final à la fin.)
Peut-être un peu plus intéressant dans votre cas particulier, le format Maildir ++ étend Maildir pour ajouter la prise en charge du quota de boîte aux lettres et le fait en mettant à jour atomiquement un fichier avec un nom fixe à l'intérieur de la boîte aux lettres (de sorte que cela pourrait être plus proche de votre B.) Je pense que Maildir ++ essaie à ajouter, ce qui n'est pas vraiment sûr sur NFS, mais il existe une approche de recalcul qui utilise une procédure similaire à celle-ci et elle est valide en tant que remplacement atomique.
Espérons que tous ces pointeurs vous seront utiles!
la source
Vous pouvez écrire un programme pour cela.
Utilisez
open(O_CREAT|O_RDWD)
pour ouvrir le fichier cible, lire tous les octets et les métadonnées pour vérifier si le fichier cible est complet, sinon, il y a deux possibilités,Écriture incomplète
Un autre processus exécute le même programme.
Essayez d'acquérir un verrou de description de fichier ouvert sur le fichier cible.
L'échec signifie qu'il y a un processus simultané, le processus actuel devrait exister.
Le succès signifie que la dernière écriture s'est écrasée, vous devez recommencer ou essayer de le réparer en écrivant dans le fichier.
Notez également que vous feriez mieux
fsync()
après avoir écrit dans le fichier cible avant de fermer le fichier et de libérer le verrou, ou tout autre processus pourrait lire des données pas encore sur le disque.https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html
Ceci est important pour vous aider à faire la distinction entre un programme s'exécutant simultanément et une opération qui s'est finalement arrêtée.
la source
Vous obtiendrez le résultat correct en faisant un
cp
ensemble avecmv
. Cela remplacera soit «B» par une nouvelle copie de «A», soit laissera «B» tel qu'il était auparavant.mise à jour pour accueillir l'existant
B
:Ce n'est pas 100% atomique, mais ça se rapproche. Il y a une condition de concurrence où deux de ces choses fonctionnent, les deux entrent dans le
if
test en même temps, les deux voient que celaB
n'existe pas, puis exécutent tous les deux lemv
.la source
mv B.tmp B
ne s'exécutera que s'ilcp A B.tmp
s'exécute et renvoie un code de résultat de réussite. comment est-ce un échec? aussi, je suis d'accord quecp A B.tmp
cela remplacerait un existantB.tmp
qui est ce que vous voulez faire. Les&&
garanties que la 2e commande s'exécutera si et seulement si la première se termine normalement.