Ce que rm refuse déjà, et ce qu'il ne refuse pas
Le rm de GNU refuse rm -rf / par défaut. Cela protège un seul chemin littéral. Les accidents courants ont une autre forme :
rm -rf /*: le shell remplace d'abord l'étoile, doncrmreçoit/bin,/etcet le reste, et aucun d'eux n'est/.rm -rf /var/app /*: une espace de trop ajoute tout le système à la liste.- Une variable vide dans un script :
rm -rf "$DIR"/*devientrm -rf /*. - La bonne commande dans le mauvais terminal, sur un serveur que vous pensiez être la préproduction.
--preserve-root=all va plus loin et refuse tout dossier situé sur un autre disque que son parent, comme un volume monté :
rm: skipping '/dev/shm', since it's on a different device
rm: and --preserve-root=all is in effect
Les habitudes comme lister un glob avec echo avant de le supprimer sont dans comment éviter les erreurs sur les serveurs de production.
Faire demander rm une seule fois
Mettez ces deux lignes dans ~/.bashrc sur le serveur, et dans /root/.bashrc :
alias rm='rm -I --preserve-root=all'
alias sudo='sudo '
-I demande une fois avant une suppression récursive ou avant de supprimer plus de trois fichiers. Toute réponse autre que y ne supprime rien. L'espace finale dans l'alias sudo dit à bash de traiter aussi le mot suivant comme un alias, donc sudo rm demande aussi :
$ sudo rm /tmp/s/*
rm: remove 4 arguments? n
zsh demande de lui-même avant rm * et rm path/*, sauf si RM_STAR_SILENT est activé :
zsh: sure you want to delete all 2 files in /tmp/z [yn]? n
Les alias ne s'appliquent qu'aux shells interactifs. Les scripts, les tâches cron et ssh server 'rm ...' utilisent le vrai rm, et \rm ou command rm contournent l'alias exprès. Évitez alias rm='rm -i' : une question pour chaque fichier habitue à taper -f.
safe-rm : des chemins que rm ignorera
safe-rm place une enveloppe devant rm qui ignore tout chemin d'une liste protégée. Sous Debian et Ubuntu :
sudo apt install safe-rm
echo '/srv/app/shared' | sudo tee -a /etc/safe-rm.conf
La liste contient déjà les dossiers système comme /etc, /usr et /var. Notre test :
$ rm -rf /srv/app/shared
safe-rm: Skipping /srv/app/shared.
Connaissez ses limites avant de compter dessus :
- Il protège le chemin listé, pas son contenu.
rm -rf /srv/app/shared/*a quand même vidé le dossier. Une ligne comme/srv/data/*protège chaque élément placé directement sous/srv/data. - Il sort avec 0 même quand il a ignoré quelque chose, donc un script ne s'en aperçoit pas.
- Le paquet ajoute son dossier au
PATHdans/etc/profile.d, donc il marche dans les shells de connexion et avecsudo -i.sudo rmutilise lesecure_pathde sudo et a lancé le vrairmdans notre test, tout commessh server 'rm ...'.
Protéger les variables dans les scripts
La plupart des catastrophes en script viennent d'une variable vide ou non définie. Arrêtez le script avant qu'il n'atteigne rm :
#!/usr/bin/env bash
set -euo pipefail
rm -rf -- "${BUILD_DIR:?}/"
s.sh: line 3: BUILD_DIR: parameter null or not set
${BUILD_DIR:?} s'arrête sur une erreur quand la variable est vide ou absente, et set -u fait de même pour toute variable non définie du script. -- termine les options, donc un fichier nommé -rf est traité comme un fichier.
Droits et fichiers immuables
La meilleure protection, c'est de ne pas pouvoir supprimer. Travaillez avec un utilisateur normal et n'utilisez sudo que pour les commandes qui en ont besoin, pour qu'une erreur dans votre dossier personnel ne puisse pas toucher le système. Voir comment créer un utilisateur SSH avec sudo.
Pour les fichiers qui ne doivent pas changer, même pour root, posez l'attribut immuable, sur ext4 et les autres systèmes de fichiers qui le gèrent :
sudo chattr +i /etc/app/config.yml
lsattr /etc/app/config.yml
----i---------e------- /etc/app/config.yml
rm: cannot remove '/etc/app/config.yml': Operation not permitted
La même erreur a arrêté rm -rf sur le dossier qui le contient. Retirez l'attribut avec sudo chattr -i avant une modification prévue. Les mises à jour et les éditeurs ne peuvent pas non plus écrire dans le fichier, alors gardez l'attribut pour les fichiers qui en ont vraiment besoin.
Une corbeille au lieu de rm
Quand vous faites le ménage à la main, déplacez plutôt les fichiers vers une corbeille. trash-cli utilise la même corbeille que les bureaux Linux :
sudo apt install trash-cli
trash-put old-release/
trash-list
trash-restore
trash-empty 30
trash-restore demande quel élément remettre en place. trash-empty 30 supprime ce qui est dans la corbeille depuis plus de 30 jours, ce qui convient à une tâche cron. Jusque-là, les fichiers occupent toujours de l'espace disque. Ne faites pas de rm un alias de trash-put : les options diffèrent, et l'habitude vous lâche sur les serveurs qui ne l'ont pas.
Snapshots, sauvegardes, et si c'est déjà arrivé
Chaque protection ci-dessus peut être contournée. Un snapshot, lui, ne se supprime pas avec rm. Prenez-en un avant un travail risqué : zfs snapshot pool/data@before-cleanup sur ZFS, btrfs subvolume snapshot -r sur btrfs, lvcreate --snapshot sur LVM, ou un snapshot de volume dans la console de votre hébergeur. Copiez les sauvegardes sur une autre machine, par exemple avec rsync en SSH, et testez une restauration de temps en temps.
Si des fichiers ont déjà disparu, arrêtez d'écrire sur ce disque : de nouvelles données peuvent écraser ce qu'un outil de récupération pourrait encore trouver. Restaurez ensuite depuis un snapshot ou une sauvegarde. Un cas peut être sauvé tout de suite : un fichier encore ouvert par un programme en cours. Trouvez-le et recopiez-le avant que ce programme ne s'arrête :
lsof +L1
cp /proc/374/fd/3 /root/data.recovered
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
tail 374 root 3r REG 0,186 2 0 12873355 /srv/data.txt (deleted)
Le nombre après /proc/ est le PID, et celui après fd/ est le nombre de la colonne FD sans sa lettre.
Questions
rm -rf / fonctionne-t-il encore sous Linux ?
Pas avec le rm de GNU : il refuse / sauf avec --no-preserve-root. Mais le shell transforme rm -rf /* en /bin, /etc et le reste, et cela n'est pas refusé.
Peut-on annuler un rm -rf ?
Pas avec rm lui-même. Restaurez depuis un snapshot ou une sauvegarde. Un fichier encore ouvert par un programme en cours peut être recopié depuis /proc/PID/fd tant que ce programme ne le ferme pas.
alias rm='rm -i' est-il une bonne idée ?
Il demande pour chaque fichier, alors on prend l'habitude de répondre oui sans lire ou d'ajouter -f. rm -I demande une seule fois, pour trois fichiers ou plus ou pour une suppression récursive, ce qui garde la question utile.
safe-rm protège-t-il les fichiers à l'intérieur d'un dossier ?
Non. Il ignore les chemins listés dans sa configuration. rm -rf /srv/app/shared/* vide quand même un /srv/app/shared protégé, sauf si son contenu est listé aussi, par exemple avec /srv/app/shared/*.
Sources
- Manuel GNU coreutils : rm
- Projet safe-rm
- chattr(1), manuel Linux
- Projet trash-cli
- Manuel de zsh : RM_STAR_SILENT
- OpenZFS : zfs-snapshot(8)