Un jour, lors d'un entretien, on m'a demandé ce que je ferais si je découvre un service non fonctionnel parce que l'espace disque est saturé.
Bien sûr, j'ai répondu que je vérifierais ce qui occupe cet espace et si possible, je libérerais de l'espace.
L'intervieweur a alors demandé ce que je ferais si la partition n'a pas d'espace libre, mais que je ne vois pas de fichiers occupant tout l'espace.
À cela, j'ai répondu qu'on peut toujours vérifier les descripteurs de fichiers ouverts, par exemple avec la commande lsof, pour comprendre quelle application a rempli tout l'espace disponible, et ensuite agir en fonction des circonstances, selon que les données sont nécessaires ou non.
L'intervieweur m'a interrompu alors que je terminais ma phrase, ajoutant à sa question : « Supposons que les données ne soient pas nécessaires, que ce soit juste un journal de débogage, mais que l'application ne fonctionne pas parce qu'elle ne peut pas écrire le débogage ? »
« Ok », ai-je répondu, « nous pouvons désactiver le débogage dans la configuration de l'application et la redémarrer. »
L'intervieweur a rétorqué : « Non, nous ne pouvons pas redémarrer l'application, nous avons encore des données importantes en mémoire, et des clients importants sont connectés au service, que nous ne pouvons pas demander de se reconnecter. »
« Très bien », ai-je dit, « si nous ne pouvons pas redémarrer l'application et que les données ne sont pas importantes, nous pouvons simplement vider ce fichier ouvert via le descripteur de fichier, même si nous ne le voyons pas avec la commande ls dans le système de fichiers. »
L'intervieweur était satisfait, mais pas moi.
Puis j'ai réfléchi, pourquoi la personne qui teste mes connaissances ne creuse-t-elle pas plus profondément ? Et si les données sont en fait importantes ? Que faire si nous ne pouvons pas redémarrer le processus et que ce processus écrit sur le système de fichiers dans une partition sans espace libre ? Que faire si nous ne pouvons pas perdre non seulement les données déjà écrites, mais aussi les données que ce processus écrit ou essaie d'écrire ?
Tuzik
Au début de ma carrière, j'ai essayé de créer une petite application qui devait stocker des informations sur les utilisateurs. À ce moment-là, je me suis demandé comment associer un utilisateur à ses données. Par exemple, j'ai Ivanov Ivan Ivanovitch, et il a certaines données, mais comment les relier ? Je peux indiquer directement qu'un chien nommé « Tuzik » appartient à cet Ivan. Mais que se passe-t-il s'il change de nom et que, par exemple, il devient Olya ? Dans ce cas, notre Olya Ivanovna Ivanova ne possédera plus de chien, tandis que notre Tuzik appartiendra toujours à un Ivan inexistant. Cette problématique a été résolue par une base de données qui attribuait à chaque utilisateur un identifiant unique (ID), et mon Tuzik était lié à cet ID, qui n'était en réalité qu'un numéro d'ordre. Ainsi, le propriétaire de Tuzik avait l'ID numéro 2, et à un moment donné, cet ID était celui d'Ivan, puis il est devenu celui d'Olya. La question de l'humanité et de l'élevage a été pratiquement résolue.
Descripteur de fichier
Le problème du fichier et du programme qui interagit avec ce fichier est assez similaire à celui de notre chien et de l'homme. Supposons que j'ouvre un fichier nommé ivan.txt et que je commence à y écrire le mot tuzik, mais que je n'ai réussi à écrire que la première lettre « t » dans le fichier, et que ce fichier a été renommé par quelqu'un en olya.txt. Cependant, le fichier reste le même, et je veux toujours y écrire mon Tuzik. Chaque fois que j'ouvre le fichier via un appel système dans n'importe quel langage de programmation, je reçois un ID unique qui me pointe vers le fichier, cet ID est le descripteur de fichier. Peu importe ce que l'on fait avec ce fichier par la suite, il peut être supprimé, renommé, changer de propriétaire ou perdre ses droits de lecture et d'écriture, j'aurai toujours accès à lui car au moment de l'ouverture du fichier, j'avais les droits pour le lire et/ou écrire et j'avais commencé à travailler avec lui, donc je dois continuer à le faire.
Sous Linux, la bibliothèque libc ouvre 3 descripteurs de fichier pour chaque application (processus) en cours d'exécution, avec les numéros 0, 1, 2. Vous pouvez trouver plus d'informations dans les liens et
- Le descripteur de fichier 0 est appelé STDIN et est associé à l'entrée de données de l'application.
- Le descripteur de fichier 1 s'appelle STDOUT et est utilisé par les applications pour afficher des données, comme les commandes print.
- Le descripteur de fichier 2 s'appelle STDERR et est utilisé par les applications pour afficher des données d'erreur.
Si vous ouvrez un fichier pour lecture ou écriture dans votre programme, vous obtiendrez probablement le premier ID libre, et ce sera le numéro 3.
La liste des descripteurs de fichiers peut être consultée pour n'importe quel processus si vous connaissez son PID.
Par exemple, ouvrons une console avec bash et consultons le PID de notre processus.
[user@localhost ]$ echo $$
15771
Dans la deuxième console, lançons
[user@localhost ]$ ls -lah /proc/15771/fd/
total 0
dr-x------ 2 user user 0 Oct 7 15:42 .
dr-xr-xr-x 9 user user 0 Oct 7 15:42 ..
lrwx------ 1 user user 64 Oct 7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 255 -> /dev/pts/21
Vous pouvez ignorer le descripteur de fichier numéro 255 dans cet article, il a été ouvert pour ses propres besoins par bash, et non par une bibliothèque liée.
Actuellement, les 3 descripteurs de fichiers sont liés à un périphérique de pseudo-terminal. , mais nous pouvons toujours les manipuler, par exemple, lançons dans la deuxième console
[user@localhost ]$ echo "hello world" > /proc/15771/fd/0
Et dans la première console, nous verrons
[user@localhost ]$ hello world
Redirection et Pipe
Vous pouvez facilement redéfinir ces 3 descripteurs de fichiers dans n'importe quel processus, y compris dans bash, par exemple via un pipe reliant deux processus, voyons
[user@localhost ]$ cat /dev/zero | sleep 10000
Vous pouvez exécuter cette commande avec strace -f et voir ce qui se passe à l'intérieur, mais je vais brièvement l'expliquer.
Notre processus parent bash avec le PID 15771 analyse notre commande et comprend combien de commandes nous voulons exécuter, dans notre cas deux : cat et sleep. Bash sait qu'il doit créer deux processus fils et les relier par un pipe. En tout, bash nécessitera 2 processus fils et un pipe.
Avant de créer les processus fils, bash lance un appel système et obtient de nouveaux descripteurs de fichiers sur un tampon pipe temporaire, mais ce tampon ne relie pas encore nos deux processus fils.
Pour le processus parent, cela ressemble à ce que le pipe est déjà présent, alors que les processus fils ne le sont pas encore :
PID commande
15771 bash
lrwx------ 1 user user 64 Oct 7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct 7 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct 7 15:42 255 -> /dev/pts/21
Ensuite, avec l'appel système bash crée deux processus enfants, et nos trois processus apparaîtront comme suit :
PID commande
15771 bash
lrwx------ 1 user user 64 Oct 7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct 7 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct 7 15:42 255 -> /dev/pts/21
PID commande
9004 bash
lrwx------ 1 user user 64 Oct 7 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct 7 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct 7 15:57 255 -> /dev/pts/21
PID commande
9005 bash
lrwx------ 1 user user 64 Oct 7 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct 7 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct 7 15:57 255 -> /dev/pts/21
N'oublions pas que clone clone le processus avec tous les descripteurs de fichiers, donc dans le processus parent et dans les enfants, ils seront identiques. La tâche du processus parent avec le PID 15771 est de surveiller les processus enfants, donc il attend simplement une réponse des enfants.
Par conséquent, le pipe n'est pas nécessaire, et il ferme les descripteurs de fichiers numérotés 3 et 4.
Dans le premier processus enfant bash avec le PID 9004, l'appel système , change notre descripteur de fichier STDOUT avec le numéro 1 en un descripteur de fichier pointant vers le pipe, dans notre cas c'est le numéro 3. Ainsi, tout ce que le premier processus enfant avec le PID 9004 écrira dans STDOUT sera automatiquement envoyé dans le tampon du pipe.
Dans le second processus enfant avec le PID 9005, bash modifie, à l'aide de dup2, le descripteur de fichier STDIN avec le numéro 0. Maintenant, tout ce que l'autre bash avec le PID 9005 lira, le lira à partir du pipe.
Ensuite, dans les processus enfants, les descripteurs de fichiers numérotés 3 et 4 sont également fermés, car ils ne sont plus utilisés.
Je ne tiens pas compte du descripteur de fichier 255, il est utilisé pour les besoins internes de bash et sera également fermé dans les processus enfants.
Ensuite, dans le premier processus enfant avec le PID 9004, bash lance, à l'aide de l'appel système le fichier exécutable que nous avons spécifié dans la ligne de commande, dans notre cas c'est /usr/bin/cat.
Dans le second processus enfant avec le PID 9005, bash lance le second fichier exécutable que nous avons spécifié, dans notre cas c'est /usr/bin/sleep.
L'appel système exec ne ferme pas les descripteurs de fichiers s'ils n'ont pas été ouverts avec le drapeau O_CLOEXEC lors de l'appel à open. Dans notre cas, après le lancement de fichiers exécutables, tous les descripteurs de fichiers actuels resteront ouverts.
Vérifions dans la console :
[user@localhost ]$ pgrep -P 15771
9004
9005
[user@localhost ]$ ls -lah /proc/15771/fd/
total 0
dr-x------ 2 user user 0 Oct 7 15:42 .
dr-xr-xr-x 9 user user 0 Oct 7 15:42 ..
lrwx------ 1 user user 64 Oct 7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:42 255 -> /dev/pts/21
[user@localhost ]$ ls -lah /proc/9004/fd
total 0
dr-x------ 2 user user 0 Oct 7 15:57 .
dr-xr-xr-x 9 user user 0 Oct 7 15:57 ..
lrwx------ 1 user user 64 Oct 7 15:57 0 -> /dev/pts/21
l-wx------ 1 user user 64 Oct 7 15:57 1 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct 7 15:57 2 -> /dev/pts/21
lr-x------ 1 user user 64 Oct 7 15:57 3 -> /dev/zero
[user@localhost ]$ ls -lah /proc/9005/fd
total 0
dr-x------ 2 user user 0 Oct 7 15:57 .
dr-xr-xr-x 9 user user 0 Oct 7 15:57 ..
lr-x------ 1 user user 64 Oct 7 15:57 0 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct 7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct 7 15:57 2 -> /dev/pts/21
[user@localhost ]$ ps -up 9004
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
user 9004 0.0 0.0 107972 620 pts/21 S+ 15:57 0:00 cat /dev/zero
[user@localhost ]$ ps -up 9005
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
user 9005 0.0 0.0 107952 360 pts/21 S+ 15:57 0:00 sleep 10000
Comme vous pouvez le constater, le numéro unique de notre pipe est identique dans les deux processus. Ainsi, nous avons une connexion entre deux processus différents avec un parent commun.
Pour ceux qui ne sont pas familiers avec les appels système utilisés par bash, je recommande fortement d'exécuter les commandes avec strace et de voir ce qui se passe à l'intérieur, par exemple comme ceci :
strace -s 1024 -f bash -c "ls | grep hello"
Revenons à notre problème de manque d'espace disque et à la tentative de sauvegarder des données sans redémarrer le processus. Écrivons un petit programme qui écrira sur le disque environ 1 mégaoctet par seconde. Dans le cas où nous ne parvenons pas à écrire des données sur le disque pour une raison quelconque, nous allons simplement ignorer cela et essayer d'écrire les données à nouveau après une seconde. Dans cet exemple, j'utilise Python, vous pouvez utiliser n'importe quel autre langage de programmation.
[user@localhost ]$ cat openforwrite.py
import datetime
import time
mystr="a"*1024*1024+"n"
with open("123.txt", "w") as f:
while True:
try:
f.write(str(datetime.datetime.now()))
f.write(mystr)
f.flush()
time.sleep(1)
except:
pass
Lançons le programme et examinons les descripteurs de fichiers.
[user@localhost ]$ python openforwrite.py &
[1] 3762
[user@localhost ]$ ps axuf | grep [o]penforwrite
user 3762 0.0 0.0 128600 5744 pts/22 S+ 16:28 0:00 | _ python openforwrite.py
[user@localhost ]$ ls -la /proc/3762/fd
total 0
dr-x------ 2 user user 0 Oct 7 16:29 .
dr-xr-xr-x 9 user user 0 Oct 7 16:29 ..
lrwx------ 1 user user 64 Oct 7 16:29 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 7 16:29 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 7 16:29 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct 7 16:29 3 -> /home/user/123.txt
On constate que nous avons nos 3 descripteurs de fichiers standards et un autre que nous avons ouvert. Vérifions la taille du fichier :
[user@localhost ]$ ls -lah 123.txt
-rw-rw-r-- 1 user user 117M Oct 7 16:30 123.txt
Les données s'écrivent, essayons de changer les droits sur le fichier :
[user@localhost ]$ sudo chown root: 123.txt
[user@localhost ]$ ls -lah 123.txt
-rw-rw-r-- 1 root root 168M Oct 7 16:31 123.txt
[user@localhost ]$ ls -lah 123.txt
-rw-rw-r-- 1 root root 172M Oct 7 16:31 123.txt
Nous voyons que les données sont toujours en train d'écrire, bien que notre utilisateur n'ait pas le droit d'écrire dans le fichier. Essayons de le supprimer :
[user@localhost ]$ sudo rm 123.txt
[user@localhost ]$ ls 123.txt
ls: impossible d'accéder à 123.txt: Aucun fichier ou répertoire de ce type
Où les données s'écrivent-elles ? Et s'écrivent-elles vraiment ? Vérifions :
[user@localhost ]$ ls -la /proc/3762/fd
total 0
dr-x------ 2 user user 0 Oct 7 16:29 .
dr-xr-xr-x 9 user user 0 Oct 7 16:29 ..
lrwx------ 1 user user 64 Oct 7 16:29 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 7 16:29 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 7 16:29 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct 7 16:29 3 -> /home/user/123.txt (deleted)
Oui, notre descripteur de fichier existe toujours, et nous pouvons travailler avec ce descripteur de fichier comme avec notre ancien fichier, nous pouvons le lire, le vider et le copier.
Regardons la taille du fichier :
[user@localhost ]$ lsof | grep 123.txt
python 31083 user 3w REG 8,5 19923457 2621522 /home/user/123.txt
La taille du fichier est de 19923457. Essayons de vider le fichier :
[user@localhost ]$ truncate -s 0 /proc/31083/fd/3
[user@localhost ]$ lsof | grep 123.txt
python 31083 user 3w REG 8,5 136318390 2621522 /home/user/123.txt
Comme nous le voyons, la taille du fichier n'augmente que et notre truncation n'a pas fonctionné. Référons-nous à la documentation sur les appels système. Si nous utilisons le flag O_APPEND lors de l'ouverture du fichier, à chaque écriture, le système d'exploitation vérifie la taille du fichier et écrit les données à la fin du fichier, et cela fait de manière atomique. Cela permet à plusieurs threads ou processus d'écrire dans le même fichier. Mais dans notre code, nous n'utilisons pas ce flag. Nous pouvons voir une autre taille de fichier dans lsof après la truncation uniquement si nous ouvrons le fichier en mode ajout, ce qui signifie qu'à la place de notre code, nous devons mettre
with open("123.txt", "w") as f:
nous devons mettre
with open("123.txt", "a") as f:
Vérifions avec le flag "w"
[user@localhost ]$ strace -e trace=open python openforwrite.py 2>&1| grep 123.txt
open("123.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
et avec le drapeau « a »
[user@localhost ]$ strace -e trace=open python openforwrite.py 2>&1 | grep 123.txt
open("123.txt", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
Programmer un processus déjà en cours
Souvent, les programmeurs utilisent des débogueurs (comme GDB) ou différents niveaux de journalisation dans l'application lors de la création et du test d'un programme. Linux permet effectivement d'écrire et de modifier un programme déjà en cours, par exemple en changeant les valeurs des variables, en définissant des points d'arrêt, etc.
Pour revenir à la question originale concernant le manque d'espace disque pour écrire un fichier, essayons de simuler le problème.
Créons un fichier pour notre partition, que nous monterons comme un disque séparé :
[user@localhost ~]$ dd if=/dev/zero of=~/tempfile_for_article.dd bs=1M count=10
10+0 enregistrements lus
10+0 enregistrements écrits
10485760 octets (10 Mo) copiés, 0.00525929 s, 2.0 Go/s
[user@localhost ~]$
Créons le système de fichiers :
[user@localhost ~]$ mkfs.ext4 ~/tempfile_for_article.dd
mke2fs 1.42.9 (28-déc-2013)
/home/user/tempfile_for_article.dd n'est pas un périphérique spécial par bloc.
Continuer malgré tout ? (o,n) o
...
Écriture des superblocs et des informations de comptabilité du système de fichiers : terminé
[user@localhost ~]$
Montons le système de fichiers :
[user@localhost ~]$ sudo mount ~/tempfile_for_article.dd /mnt/
[sudo] mot de passe pour user :
[user@localhost ~]$ df -h | grep mnt
/dev/loop0 8.7M 172K 7.9M 3% /mnt
Créons un répertoire avec notre propriétaire :
[user@localhost ~]$ sudo mkdir /mnt/logs
[user@localhost ~]$ sudo chown user: /mnt/logs
Ouvrons le fichier en mode écriture uniquement dans notre programme :
with open("/mnt/logs/123.txt", "w") as f:
Lançons
[user@localhost ]$ python openforwrite.py
Attendez quelques secondes
[user@localhost ~]$ df -h | grep mnt
/dev/loop0 8.7M 8.0M 0 100% /mnt
Nous avons donc rencontré le problème décrit au début de cet article. Espace libre 0, occupé 100%.
Nous nous rappelons que selon les conditions du problème, nous essayons d'écrire des données très importantes qui ne doivent pas être perdues. Et en même temps, nous devons réparer le service sans redémarrer le processus.
Supposons que nous avons toujours de l'espace disque, mais dans une autre partition, par exemple dans /home.
Essayons de "reprogrammer à la volée" notre code.
Regardons le PID de notre processus qui a occupé tout l'espace disque :
[user@localhost ~]$ ps axuf | grep [o]penfor
user 10078 27.2 0.0 128600 5744 pts/22 R+ 11:06 0:02 | _ python openforwrite.py
Connectons-nous au processus via gdb
[user@localhost ~]$ gdb -p 10078
...
(gdb)
Regardons les descripteurs de fichiers ouverts :
(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user 0 Oct 8 11:06 .
dr-xr-xr-x 9 user user 0 Oct 8 11:06 ..
lrwx------ 1 user user 64 Oct 8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct 8 11:09 3 -> /mnt/logs/123.txt
Regardons les informations sur le descripteur de fichier numéro 3, qui nous intéresse
(gdb) shell cat /proc/10078/fdinfo/3
pos: 8189952
flags: 0100001
mnt_id: 482
En gardant à l'esprit quel appel système fait Python (voir ci-dessus où nous avons exécuté strace et trouvé l'appel open), en traitant notre code pour ouvrir un fichier, nous faisons la même chose nous-mêmes au nom de notre processus, mais les bits O_WRONLY|O_CREAT|O_TRUNC doivent être remplacés par une valeur numérique. Pour cela, nous ouvrons les sources du noyau, par exemple et regardons quels drapeaux correspondent à quoi
#define O_WRONLY 00000001
#define O_CREAT 00000100
#define O_TRUNC 00001000
Nous regroupons toutes les valeurs en une seule, obtenant 00001101
Nous lançons notre appel depuis gdb
(gdb) call open("/home/user/123.txt", 00001101,0666)
$1 = 4
Ainsi, nous avons obtenu un nouveau descripteur de fichier avec le numéro 4 et un nouveau fichier ouvert sur une autre partition, vérifions :
(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user 0 Oct 8 11:06 .
dr-xr-xr-x 9 user user 0 Oct 8 11:06 ..
lrwx------ 1 user user 64 Oct 8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct 8 11:09 3 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 Oct 8 11:15 4 -> /home/user/123.txt
Nous nous souvenons de l'exemple avec le pipe — comment bash change les descripteurs de fichiers, et avons déjà appris l'appel système dup2.
Essayons de remplacer un descripteur de fichier par un autre
(gdb) call dup2(4,3)
$2 = 3
Vérifions :
(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user 0 Oct 8 11:06 .
dr-xr-xr-x 9 user user 0 Oct 8 11:06 ..
lrwx------ 1 user user 64 Oct 8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct 8 11:09 3 -> /home/user/123.txt
l-wx------ 1 user user 64 Oct 8 11:15 4 -> /home/user/123.txt
Nous fermons le descripteur de fichier 4, car nous n'en avons pas besoin :
(gdb) call close (4)
$1 = 0
Et nous quittons gdb
(gdb) quit
Une session de débogage est active.
Inférieur 1 [processus 10078] sera détaché.
Quitter quand même ? (y ou n) y
Détachement du programme : /usr/bin/python2.7, processus 10078
Vérifions le nouveau fichier :
[user@localhost ~]$ ls -lah /home/user/123.txt
-rw-rw-r-- 1 user user 5.1M Oct 8 11:18 /home/user/123.txt
[user@localhost ~]$ ls -lah /home/user/123.txt
-rw-rw-r-- 1 user user 7.1M Oct 8 11:18 /home/user/123.txt
Comme nous le voyons, les données sont écrites dans un nouveau fichier, vérifions l'ancien :
[user@localhost ~]$ ls -lah /mnt/logs/123.txt
-rw-rw-r-- 1 user user 7.9M Oct 8 11:08 /mnt/logs/123.txt
Les données ne sont pas perdues, l'application fonctionne, les journaux sont écrits dans un nouvel emplacement.
Compliquons un peu la tâche
Imaginons que nos données sont importantes, mais qu'il n'y a pas d'espace disque disponible dans aucune des partitions et que nous ne pouvons pas connecter de disque.
Ce que nous pouvons faire, c'est rediriger nos données vers un certain endroit, par exemple dans un pipe, et rediriger les données du pipe vers le réseau via un programme comme netcat.
Nous pouvons créer un pipe nommé avec la commande mkfifo. Cela créera un pseudo-fichier sur le système de fichiers, même s'il n'y a pas d'espace libre dessus.
Nous redémarrons l'application et vérifions :
[user@localhost ]$ python openforwrite.py
[user@localhost ~]$ ps axuf | grep [o]pen
user 5946 72.9 0.0 128600 5744 pts/22 R+ 11:27 0:20 | _ python openforwrite.py
[user@localhost ~]$ ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user 0 Oct 8 11:27 .
dr-xr-xr-x 9 user user 0 Oct 8 11:27 ..
lrwx------ 1 user user 64 Oct 8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct 8 11:28 3 -> /mnt/logs/123.txt
[user@localhost ~]$ df -h | grep mnt
/dev/loop0 8.7M 8.0M 0 100% /mnt
Il n'y a pas d'espace disque, mais nous créons avec succès un pipe nommé là-bas :
[user@localhost ~]$ mkfifo /mnt/logs/megapipe
[user@localhost ~]$ ls -lah /mnt/logs/megapipe
prw-rw-r-- 1 user user 0 Oct 8 11:28 /mnt/logs/megapipe
Nous devons maintenant envelopper toutes les données qui passent par ce pipe vers un autre serveur via le réseau, pour cela, nous pouvons utiliser netcat.
Sur le serveur remote-server.example.com, nous lançons
[user@localhost ~]$ nc -l 7777 > 123.txt
Sur notre serveur problématique, nous le lançons dans un terminal séparé
[user@localhost ~]$ nc remote-server.example.com 7777 < /mnt/logs/megapipe
Maintenant, toutes les données qui entreront dans le pipe passeront automatiquement sur stdin dans netcat, qui les enverra dans le réseau au port 7777.
Tout ce qu'il nous reste à faire est de commencer à écrire nos données dans ce pipe nommé.
Nous avons déjà une application en cours :
[user@localhost ~]$ ps axuf | grep [o]pen
user 5946 99.8 0.0 128600 5744 pts/22 R+ 11:27 169:27 | _ python openforwrite.py
[user@localhost ~]$ ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user 0 Oct 8 11:27 .
dr-xr-xr-x 9 user user 0 Oct 8 11:27 ..
lrwx------ 1 user user 64 Oct 8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct 8 11:28 3 -> /mnt/logs/123.txt
Parmi tous les drapeaux, nous avons seulement besoin de O_WRONLY car le fichier existe déjà et nous n'avons pas besoin de le nettoyer.
[user@localhost ~]$ gdb -p 5946
...
(gdb) call open("/mnt/logs/megapipe", 00000001,0666)
$1 = 4
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user 0 Oct 8 11:27 .
dr-xr-xr-x 9 user user 0 Oct 8 11:27 ..
lrwx------ 1 user user 64 Oct 8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct 8 11:28 3 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 Oct 8 14:20 4 -> /mnt/logs/megapipe
(gdb) call dup2(4,3)
$2 = 3
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user 0 Oct 8 11:27 .
dr-xr-xr-x 9 user user 0 Oct 8 11:27 ..
lrwx------ 1 user user 64 Oct 8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct 8 11:28 3 -> /mnt/logs/megapipe
l-wx------ 1 user user 64 Oct 8 14:20 4 -> /mnt/logs/megapipe
(gdb) call close(4)
$3 = 0
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user 0 Oct 8 11:27 .
dr-xr-xr-x 9 user user 0 Oct 8 11:27 ..
lrwx------ 1 user user 64 Oct 8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct 8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct 8 11:28 3 -> /mnt/logs/megapipe
(gdb) quit
A debugging session is active.
Inferior 1 [process 5946] will be detached.
Quit anyway? (y or n) y
Detaching from program: /usr/bin/python2.7, process 5946
Nous vérifions le serveur distant remote-server.example.com
[user@localhost ~]$ ls -lah 123.txt
-rw-rw-r-- 1 user user 38M Oct 8 14:21 123.txt
Les données sont en cours, nous vérifions le serveur problématique
[user@localhost ~]$ ls -lah /mnt/logs/
total 7.9M
drwxr-xr-x 2 user user 1.0K Oct 8 11:28 .
drwxr-xr-x 4 root root 1.0K Oct 8 10:55 ..
-rw-rw-r-- 1 user user 7.9M Oct 8 14:17 123.txt
prw-rw-r-- 1 user user 0 Oct 8 14:22 megapipe
Les données ont été sauvegardées, le problème est résolu.
Profitant de l’occasion, je salue mes collègues de l'entreprise Degiro.
Écoutez les podcasts de Radio-T.
À tous, je vous souhaite le meilleur.
En guise de devoir à domicile, je vous propose de réfléchir à ce que contiendront les descripteurs de fichiers des processus cat et sleep si vous exécutez une telle commande :
[user@localhost ~]$ cat /dev/zero 2>/dev/null| sleep 10000
Source : habr.com
