Remplacement de fichiers en général
Tout d'abord, il existe plusieurs stratégies pour remplacer un fichier:
Ouvrez le fichier existant pour l'écriture, tronquez-le à la longueur 0 et écrivez le nouveau contenu. (Une variante moins courante consiste à ouvrir le fichier existant, à remplacer l'ancien contenu par le nouveau contenu, à tronquer le fichier à la nouvelle longueur s'il est plus court.) En termes de shell:
echo 'new content' >somefile
Supprimez l'ancien fichier et créez un nouveau fichier du même nom. En termes de coquille:
rm somefile
echo 'new content' >somefile
Écrivez dans un nouveau fichier sous un nom temporaire, puis déplacez le nouveau fichier vers le nom existant. Le déplacement supprime l'ancien fichier. En termes de coquille:
echo 'new content' >somefile.new
mv somefile.new somefile
Je ne vais pas énumérer toutes les différences entre les stratégies, je vais juste en mentionner quelques-unes qui sont importantes ici. Avec la stratégie 1, si un processus utilise actuellement le fichier, le processus voit le nouveau contenu lors de sa mise à jour. Cela peut entraîner une certaine confusion si le processus s'attend à ce que le contenu du fichier reste le même. Notez que cela concerne uniquement les processus qui ont le fichier ouvert (comme visible dans lsof
ou dans ; les applications interactives qui ont un document ouvert (par exemple, ouvrir un fichier dans un éditeur) ne gardent généralement pas le fichier ouvert, elles chargent le contenu du fichier pendant la "Ouvrir le document" et ils remplacent le fichier (en utilisant l'une des stratégies ci-dessus) pendant l'opération "enregistrer le document"./proc/PID/fd/
Avec les stratégies 2 et 3, si un processus a le fichier somefile
ouvert, l'ancien fichier reste ouvert pendant la mise à niveau du contenu. Avec la stratégie 2, l'étape de suppression du fichier ne supprime en fait que l'entrée du fichier dans le répertoire. Le fichier lui-même n'est supprimé que s'il ne contient aucune entrée de répertoire (sur les systèmes de fichiers Unix typiques, il peut y avoir plusieurs entrées de répertoire pour le même fichier ) et qu'aucun processus ne l'a ouvert. Voici un moyen d'observer cela - le fichier n'est supprimé que lorsque le sleep
processus est tué ( rm
supprime uniquement son entrée de répertoire).
echo 'old content' >somefile
sleep 9999999 <somefile &
df .
rm somefile
df .
cat /proc/$!/fd/0
kill $!
df .
Avec la stratégie 3, l'étape de déplacement du nouveau fichier vers le nom existant supprime l'entrée de répertoire menant à l'ancien contenu et crée une entrée de répertoire menant au nouveau contenu. Cela se fait en une seule opération atomique, donc cette stratégie a un avantage majeur: si un processus ouvre le fichier à tout moment, il verra l'ancien contenu ou le nouveau contenu - il n'y a aucun risque d'obtenir du contenu mixte ou du fichier non existant.
Remplacement des exécutables
Si vous essayez la stratégie 1 avec un exécutable en cours d'exécution sur Linux, vous obtiendrez une erreur.
cp /bin/sleep .
./sleep 999999 &
echo oops >|sleep
bash: sleep: Text file busy
Un «fichier texte» signifie un fichier contenant du code exécutable pour des raisons historiques obscures . Linux, comme beaucoup d'autres variantes d'Unix, refuse d'écraser le code d'un programme en cours d'exécution; quelques variantes Unix le permettent, entraînant des plantages à moins que le nouveau code ne soit une modification très bien pensée de l'ancien code.
Sous Linux, vous pouvez remplacer le code d'une bibliothèque chargée dynamiquement. Il est susceptible d'entraîner une panne du programme qui l'utilise. (Vous ne pourrez peut-être pas observer cela avec sleep
car il charge tout le code de bibliothèque dont il a besoin au démarrage. Essayez un programme plus complexe qui fait quelque chose d'utile après avoir dormi, comme perl -e 'sleep 9; print lc $ARGV[0]'
.)
Si un interpréteur exécute un script, le fichier de script est ouvert de manière ordinaire par l'interpréteur, il n'y a donc aucune protection contre l'écrasement du script. Certains interprètes lisent et analysent l'intégralité du script avant de commencer à exécuter la première ligne, d'autres lisent le script si nécessaire. Voir Que se passe-t-il si vous modifiez un script pendant l'exécution? et comment Linux traite-t-il les scripts shell? pour plus de détails.
Les stratégies 2 et 3 sont également sans danger pour les exécutables: bien que l'exécution d'exécutables (et de bibliothèques chargées dynamiquement) ne soit pas des fichiers ouverts dans le sens d'avoir un descripteur de fichier, ils se comportent de manière très similaire. Tant qu'un programme exécute le code, le fichier reste sur le disque même sans entrée de répertoire.
Mettre à niveau une application
La plupart des gestionnaires de packages utilisent la stratégie 3 pour remplacer les fichiers, en raison de l'avantage majeur mentionné ci-dessus - à tout moment, l'ouverture du fichier conduit à une version valide de celui-ci.
Là où les mises à niveau d'application peuvent se briser, c'est que si la mise à niveau d'un fichier est atomique, la mise à niveau de l'application dans son ensemble ne l'est pas si l'application se compose de plusieurs fichiers (programme, bibliothèques, données,…). Considérez la séquence d'événements suivante:
- Une instance de l'application est démarrée.
- L'application est mise à niveau.
- L'application d'instance en cours d'exécution ouvre l'un de ses fichiers de données.
À l'étape 3, l'instance en cours d'exécution de l'ancienne version de l'application ouvre un fichier de données à partir de la nouvelle version. Que cela fonctionne ou non dépend de l'application, de quel fichier il s'agit et de la quantité de fichier modifié.
Après une mise à niveau, vous remarquerez que l'ancien programme est toujours en cours d'exécution. Si vous souhaitez exécuter la nouvelle version, vous devrez quitter l'ancien programme et exécuter la nouvelle version. Les gestionnaires de packages tuent et redémarrent généralement les démons lors d'une mise à niveau, mais laissent les applications des utilisateurs finaux tranquilles.
Quelques démons ont des procédures spéciales pour gérer les mises à niveau sans avoir à tuer le démon et à attendre que la nouvelle instance redémarre (ce qui provoque une interruption de service). Cela est nécessaire dans le cas d' init , qui ne peut pas être tué; Les systèmes init fournissent un moyen de demander à l'appel d'instance en cours execve
de se remplacer par la nouvelle version.