Conserver (ou restaurer) les autorisations de fichier lors du remplacement de fichier

11

J'ai une commande qui accepte un fichier comme argument, modifie le fichier, puis l'écrit dans le nom de fichier spécifié dans le deuxième argument. J'appellerai ce programme modifyfile.

Je voulais qu'il fonctionne "sur place", j'ai donc écrit un script shell (bash) qui le modifie en un fichier temporaire puis le recule:

TMP=`mktemp`
modifyfile "$original" "$TMP"
mv -v "$TMP" "$original"

Cela a pour effet secondaire malheureux de détruire les autorisations sur ce fichier. Le fichier est recréé avec les autorisations par défaut.

Existe-t-il un moyen de dire à la mvcommande d'écraser la destination sans modifier ses autorisations? Ou existe-t-il un moyen de sauvegarder l'utilisateur, le groupe et les autorisations de l'original et de les restaurer?

Stephen Ostermiller
la source

Réponses:

10

Au lieu d'utiliser mv, redirigez simplement cat. Par exemple:

TMP=$(mktemp)
modifyfile "$original" "$TMP"
cat "$TMP" > "$original"

Cela écrase $originalle contenu de $TMP, sans rien toucher au niveau du fichier.

strugee
la source
Cela ne pourrait-il pas être problématique si un programme a un descripteur de fichier ouvert dans le fichier?
Martin von Wittich
2
Je dois rm "$TMP"aussi le faire aussi, mais il semble faire exactement ce que je veux.
Stephen Ostermiller
@MartinvonWittich, ce serait probablement un problème si vous l'utilisiez à la mvplace. Je ne vois pas de moyen de résoudre ce problème.
strugee
2
@MartinvonWittich Oui. Create-new-then-move vous donne un changement atomique et n'affecte pas les programmes qui ont le fichier ouvert, mais comme il crée un nouveau fichier, la propriété et les autorisations du fichier sont perdues. Truncate-existing-then-write préserve les autorisations et la propriété, mais perd les données en cas de panne et fait glisser le tapis sous les pieds des programmes qui ont le fichier ouvert. Vous ne pouvez pas combiner les bonnes parties des deux.
Gilles 'SO- arrête d'être méchant'
1
@MartinvonWittich chownfonctionne uniquement en tant que root. chmodet chgrppeut ou peut ne pas fonctionner selon les autorisations de l'utilisateur. Ni l'un ni l'autre ne copie d'autres attributs tels que l'ACL ou les attributs étendus spécifiques au système de fichiers.
Gilles 'SO- arrête d'être méchant'
10

Il existe deux stratégies pour remplacer un fichier par une nouvelle version:

  1. Créez un fichier temporaire avec la nouvelle version, puis déplacez-le en place.

    • Avantage: si un programme ouvre ce fichier, il lira l'ancien ou le nouveau contenu, selon qu'il a ouvert le fichier avant ou après le déplacement. Il n'y a pas de confusion.
    • Avantage: en cas de plantage, l'ancien contenu est conservé.
    • Inconvénient: puisqu'un nouveau fichier est créé, les attributs du fichier (propriété, permission, etc.) ne sont pas conservés.
  2. Remplacez l'ancien fichier en place.

    • Avantage: les attributs du fichier sont conservés.
    • Inconvénient: en cas de plantage, le fichier peut être laissé à moitié écrit.
    • Inconvénient: si un programme a le fichier ouvert lors de sa mise à jour, ce programme peut lire des données incohérentes.

Si vous le pouvez, utilisez la méthode 1, mais répliquez d'abord les attributs du fichier d'origine avec cp -p --attributes-only. Cela nécessite des coreutils GNU (c'est-à-dire Linux non intégré ou des environnements suffisamment similaires à Linux). Si ce cpn'est pas le cas --attributes-only, omettez cette option: cela fonctionnera mais cela répliquera également les données.

tmp=$(mktemp)
cp -p --attributes-only "$original" "$tmp"
modifyfile "$original" "$tmp"
mv -f "$tmp" "$original"

Si vous ne pouvez pas répliquer les attributs du fichier existant, par exemple parce que vous disposez d'autorisations d'écriture sur celui-ci mais ne le possédez pas et que vous souhaitez conserver le propriétaire, alors seule la méthode 2 est possible. Pour minimiser le risque de perte de données:

  • Faites la fenêtre pendant laquelle le fichier sera incomplet aussi petite que possible. Préparez d'abord les données dans un fichier temporaire, puis copiez-les en place.
  • Faites d'abord une sauvegarde de l'ancien fichier.

tmp=$(mktemp)
backup="${original}~"
modifyfile "$original" "$tmp"
cp -p "$original" "$backup"
cp -f "$tmp" "$original"
Gilles 'SO- arrête d'être méchant'
la source
Bonne réponse! De nos jours, je suggère d'utiliser l'argument --attributes-only avec la commande cp dans la méthode 1 . De cette façon, le cp -p --attributes-only "$original" "$tmp"n'utilisera pas de ressources pour copier le contenu du fichier. Je n'ai pas pu trouver de quelle version cet argument a été ajouté.
Marcelo Barros
@MarceloBarros Il a été ajouté dans GNU coreutils 8.6 publié le 2010-10-15, donc ces jours-ci, si vous avez GNU coreutils, vous devriez l'avoir. Il n'y a toujours rien de tel avec d'autres cpimplémentations.
Gilles 'SO- arrête d'être méchant'
5

Après notre discussion sur la première réponse, je propose une réponse différente:

TMP="$(mktemp "$original".XXXXXXXXXX)"
modifyfile "$original" "$TMP"
chmod --reference="$original" "$TMP"
chown --reference="$original" "$TMP"
mv -f "$TMP" "$original"

Remarques:

  • J'utilise $originaldans le mktempmodèle pour m'assurer que le fichier temporaire n'est pas placé dans /tmpmais dans le même dossier que $original. Je crois que si /tmpest monté sur un système de fichiers différent, l'opération ne serait plus atomique.
  • Le résultat de mktempest maintenant cité au cas où il contiendrait un espace.
  • J'utilise $()au lieu de `` car je le considère plus propre.
  • ch{mod,own} --referencesont utilisés pour transférer les autorisations de $originalà $TMP. Si quelqu'un a des idées supplémentaires sur les métadonnées qui peuvent et doivent être transférées, veuillez modifier mon message et l'ajouter.
  • Eh bien, cela nécessite des autorisations root comme l'a souligné Gilles. Eh bien, je ne vais pas jeter cela maintenant que je l'ai écrit: P
Martin von Wittich
la source