Eines Tages wurde ich in einem Vorstellungsgespräch gefragt, was ich tun würde, wenn ich einen nicht funktionierenden Service aufgrund von Platzmangel auf der Festplatte entdecke.
Natürlich antwortete ich, dass ich überprüfen würde, was den Platz belegt, und falls möglich, den Speicherplatz freigeben würde.
Daraufhin fragte der Interviewer, was wäre, wenn in dem Partition kein freier Speicherplatz vorhanden ist, aber auch keine Dateien, die den gesamten Platz belegen, sichtbar sind.
Darauf sagte ich, dass ich immer die offenen Dateideskriptoren überprüfen könne, zum Beispiel mit dem Befehl lsof, um zu verstehen, welche Anwendung den gesamten verfügbaren Platz belegt hat, und dann je nach Situation weiter verfahren könnte, je nachdem, ob die Daten benötigt werden oder nicht.
Der Interviewer unterbrach mich beim letzten Wort und ergänzte seine Frage: „Angenommen, die Daten sind für uns nicht wichtig, es handelt sich nur um ein Debug-Log, aber die Anwendung funktioniert nicht, weil sie das Debug nicht schreiben kann?“
„Okay“, antwortete ich, „wir können das Debugging in der Konfiguration der Anwendung deaktivieren und sie neu starten.“
Der Interviewer widersprach: „Nein, die Anwendung können wir nicht neu starten, denn im Speicher befinden sich immer noch wichtige Daten, und wichtige Kunden sind mit dem Service verbunden, die wir nicht dazu zwingen können, sich neu zu verbinden.“
„Na gut“, sagte ich, „wenn wir die Anwendung nicht neu starten können und die Daten uns nicht wichtig sind, dann können wir diese geöffnete Datei einfach über den Dateideskriptor leeren, auch wenn wir sie nicht im ls-Befehl im Dateisystem sehen.“
Der Interviewer war zufrieden, ich jedoch nicht.
Da dachte ich, warum gräbt die Person, die mein Wissen prüft, nicht tiefer? Was ist, wenn die Daten doch wichtig sind? Was, wenn wir den Prozess nicht neu starten können und dieser Prozess auf das Dateisystem in einen Bereich schreibt, in dem kein freier Speicherplatz vorhanden ist? Was ist, wenn wir nicht nur bereits geschriebene Daten verlieren können, sondern auch die Daten, die dieser Prozess schreibt oder zu schreiben versucht?
Tuzik
Am Anfang meiner Karriere versuchte ich, eine kleine Anwendung zu erstellen, in der Informationen über Benutzer gespeichert werden sollten. Damals dachte ich darüber nach, wie ich einen Benutzer seinen Daten zuordnen kann. Nehmen wir zum Beispiel Ivan Ivanovich Ivanov, der einige Daten hat, aber wie können wir das zusammenbringen? Ich kann direkt angeben, dass der Hund namens „Tuzik“ diesem Ivan gehört. Aber was, wenn er seinen Namen ändert und anstelle von Ivan zum Beispiel Olya wird? Dann würde unsere Olya Ivanovna Ivanova keinen Hund mehr haben, während unser Tuzik immer noch zu dem nicht mehr existierenden Ivan gehören würde. Dieses Problem wurde durch eine Datenbank gelöst, die jedem Benutzer eine eindeutige Kennung (ID) zuwies, und mein Tuzik wurde mit dieser ID verknüpft, die im Grunde einfach eine Reihenfolgenummer war. So hatte der Besitzer von Tuzik die ID mit der Nummer 2, und für einen bestimmten Zeitraum war das dieser Ivan, und dann wurde es dieselbe ID, die Olya bekam. Das Problem der Menschheit und der Tierhaltung war nahezu gelöst.
Dateideskriptor
Das Problem mit der Datei und dem Programm, das mit dieser Datei arbeitet, ähnelt etwa dem unserer Hunde und Menschen. Angenommen, ich habe die Datei mit dem Namen ivan.txt geöffnet und begonnen, das Wort tuzik hineinzuschreiben, aber ich habe nur den ersten Buchstaben "t" in die Datei geschrieben, und diese Datei wurde von jemandem in olya.txt umbenannt. Die Datei ist jedoch dieselbe geblieben, und ich möchte immer noch meinen tuzik darin speichern. Jedes Mal, wenn ich die Datei über einen Systemaufruf öffne, erhalte ich in jeder Programmiersprache eine eindeutige ID, die mich auf die Datei verweist, diese ID ist der Dateideskriptor. Es spielt keine Rolle, was andere mit dieser Datei tun, sie kann gelöscht, umbenannt, der Besitzer kann geändert werden oder die Lese- und Schreibrechte können entzogen werden, ich werde trotzdem Zugriff darauf haben, denn zum Zeitpunkt des Öffnens der Datei hatte ich Rechte für das Lesen und/oder Schreiben und hatte begonnen, mit ihr zu arbeiten, also muss ich das auch weiterhin tun.
In Linux öffnet die libc-Bibliothek für jede laufende Anwendung (Prozess) 3 Dateideskriptoren mit den Nummern 0, 1, 2. Weitere Informationen finden Sie unter den Links und
- Dateideskriptor 0 wird als STDIN bezeichnet und ist mit der Eingabe von Daten für die Anwendung verknüpft.
- Dateideskriptor 1 wird als STDOUT bezeichnet und von Anwendungen für die Ausgabe von Daten verwendet, z. B. in den print-Befehlen.
- Dateideskriptor 2 wird als STDERR bezeichnet und dient Anwendungen zur Ausgabe von Fehlermeldungen.
Wenn Sie in Ihrem Programm eine Datei zum Lesen oder Schreiben öffnen, erhalten Sie höchstwahrscheinlich die erste verfügbare ID, und diese wird die Nummer 3 sein.
Die Liste der Dateideskriptoren kann bei jedem Prozess angesehen werden, wenn Sie dessen PID kennen.
Öffnen wir beispielsweise ein Terminal mit bash und schauen uns die PID unseres Prozesses an.
[user@localhost ]$ echo $$
15771
Starten Sie in der zweiten Konsole
[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
Den Dateideskriptor mit der Nummer 255 können Sie im Rahmen dieses Artikels getrost ignorieren, da er bereits von bash für eigene Zwecke geöffnet wurde und nicht von der verlinkten Bibliothek.
Momentan sind alle 3 Dateideskriptoren mit dem Gerät des Pseudoterminals verbunden. , aber wir können trotzdem damit umgehen, zum Beispiel indem wir in der zweiten Konsole starten.
[user@localhost ]$ echo "hello world" > /proc/15771/fd/0
Und in der ersten Konsole sehen wir
[user@localhost ]$ hello world
Umleitung und Pipe
Sie können diese 3 Dateideskriptoren in jedem Prozess einfach überschreiben, auch in bash, zum Beispiel über eine Pipe, die zwei Prozesse verbindet. Sehen wir uns das an:
[user@localhost ]$ cat /dev/zero | sleep 10000
Sie können diesen Befehl selbst ausführen und strace -f sehen, was intern passiert, aber ich werde es kurz erklären.
Unser übergeordneter Prozess bash mit PID 15771 parst unseren Befehl und versteht, wie viele Befehle wir tatsächlich ausführen möchten, in unserem Fall sind es zwei: cat und sleep. Bash weiß, dass es zwei untergeordnete Prozesse erstellen und sie mit einer Pipe verbinden muss. Insgesamt benötigt bash 2 untergeordnete Prozesse und eine Pipe.
Vor der Erstellung der untergeordneten Prozesse führt bash den Systemaufruf aus und erhält neue Dateideskriptoren für den temporären Pipe-Puffer, aber dieser Puffer verbindet vorerst nicht unsere beiden untergeordneten Prozesse.
Für den übergeordneten Prozess sieht es so aus, als wäre die Pipe bereits vorhanden, während die untergeordneten Prozesse noch nicht existieren:
PID Befehl
15771 bash
lrwx------ 1 user user 64 7. Okt 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 7. Okt 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 7. Okt 15:42 255 -> /dev/pts/21
Dann erstellt der Systemaufruf bash zwei Kindprozesse, und unsere drei Prozesse sehen dann so aus:
PID Befehl
15771 bash
lrwx------ 1 user user 64 7. Okt 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 7. Okt 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 7. Okt 15:42 255 -> /dev/pts/21
PID Befehl
9004 bash
lrwx------ 1 user user 64 7. Okt 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 7. Okt 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 7. Okt 15:57 255 -> /dev/pts/21
PID Befehl
9005 bash
lrwx------ 1 user user 64 7. Okt 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 7. Okt 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 7. Okt 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 7. Okt 15:57 255 -> /dev/pts/21
Denken wir daran, dass clone den Prozess zusammen mit allen Dateideskriptoren klont, weshalb diese im übergeordneten und in den untergeordneten Prozessen identisch sind. Die Aufgabe des übergeordneten Prozesses mit der PID 15771 ist es, die untergeordneten Prozesse zu überwachen, daher wartet er einfach auf die Rückmeldung von diesen.
Folglich benötigt er kein Pipe, und schließt die Dateideskriptoren mit den Nummern 3 und 4.
Im ersten untergeordneten Prozess bash mit der PID 9004, durch den Systemaufruf , ändert er unseren STDOUT-Dateideskriptor mit der Nummer 1 auf den Dateideskriptor, der auf das Pipe verweist, in unserem Fall die Nummer 3. Somit wird alles, was der erste untergeordnete Prozess mit der PID 9004 in STDOUT schreibt, automatisch in den Pipe-Puffer gelangen.
Im zweiten untergeordneten Prozess mit der PID 9005 ändert bash mit dup2 den Dateideskriptor STDIN mit der Nummer 0. Jetzt wird alles, was unser zweiter bash mit der PID 9005 liest, aus dem Pipe lesen.
Anschließend werden in den untergeordneten Prozessen ebenfalls die Dateideskriptoren mit den Nummern 3 und 4 geschlossen, da sie nicht mehr benötigt werden.
Den Dateideskriptor 255 ignoriere ich absichtlich, er wird für interne Zwecke von bash verwendet und in den untergeordneten Prozessen ebenfalls geschlossen.
Im ersten untergeordneten Prozess mit der PID 9004 startet bash über einen Systemaufruf die ausführbare Datei, die wir in der Befehlszeile angegeben haben, in unserem Fall ist das /usr/bin/cat.
Im zweiten untergeordneten Prozess mit der PID 9005 startet bash die zweite ausführbare Datei, die wir angegeben haben, in unserem Fall ist das /usr/bin/sleep.
Der Systemaufruf exec schließt die Dateibeschreibungen nicht, wenn sie beim Ausführen des Aufrufs open nicht mit dem Flag O_CLOEXEC geöffnet wurden. In unserem Fall bleiben nach dem Start der ausführbaren Dateien alle aktuellen Dateibeschreibungen erhalten.
Überprüfen wir in der Konsole:
[user@localhost ]$ pgrep -P 15771
9004
9005
[user@localhost ]$ ls -lah /proc/15771/fd/
total 0
dr-x------ 2 user user 0 Okt 7 15:42 .
dr-xr-xr-x 9 user user 0 Okt 7 15:42 ..
lrwx------ 1 user user 64 Okt 7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Okt 7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Okt 7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Okt 7 15:42 255 -> /dev/pts/21
[user@localhost ]$ ls -lah /proc/9004/fd
total 0
dr-x------ 2 user user 0 Okt 7 15:57 .
dr-xr-xr-x 9 user user 0 Okt 7 15:57 ..
lrwx------ 1 user user 64 Okt 7 15:57 0 -> /dev/pts/21
l-wx------ 1 user user 64 Okt 7 15:57 1 -> pipe:[253543032]
lrwx------ 1 user user 64 Okt 7 15:57 2 -> /dev/pts/21
lr-x------ 1 user user 64 Okt 7 15:57 3 -> /dev/zero
[user@localhost ]$ ls -lah /proc/9005/fd
total 0
dr-x------ 2 user user 0 Okt 7 15:57 .
dr-xr-xr-x 9 user user 0 Okt 7 15:57 ..
lr-x------ 1 user user 64 Okt 7 15:57 0 -> pipe:[253543032]
lrwx------ 1 user user 64 Okt 7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Okt 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
Wie Sie sehen, stimmt die eindeutige Nummer unserer Pipe in beiden Prozessen überein. So haben wir eine Verbindung zwischen zwei verschiedenen Prozessen mit einem übergeordneten Prozess.
Für diejenigen, die mit den Systemaufrufen, die bash verwendet, nicht vertraut sind, empfehle ich dringend, die Befehle über strace auszuführen und zu beobachten, was im Inneren passiert, zum Beispiel so:
strace -s 1024 -f bash -c "ls | grep hello"
Kehren wir zu unserem Problem mit dem Mangel an Speicherplatz auf der Festplatte zurück und dem Versuch, Daten ohne Neustart des Prozesses zu speichern. Wir werden ein kleines Programm schreiben, das etwa 1 Megabyte pro Sekunde auf die Festplatte schreibt. Wenn wir aus irgendeinem Grund die Daten nicht auf die Festplatte schreiben können, ignorieren wir das einfach und versuchen es in einer Sekunde erneut. Im Beispiel verwende ich Python, Sie können jede andere Programmiersprache verwenden.
[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
Lassen Sie uns das Programm ausführen und die Dateideskriptoren ansehen.
[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
Wie wir sehen, haben wir hier unsere 3 Standard-Dateideskriptoren und einen weiteren, den wir geöffnet haben. Überprüfen wir die Dateigröße:
[user@localhost ]$ ls -lah 123.txt
-rw-rw-r-- 1 user user 117M Oct 7 16:30 123.txt
Die Daten werden geschrieben, wir versuchen die Berechtigungen für die Datei zu ändern:
[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
Wir sehen, dass die Daten immer noch geschrieben werden, obwohl unser Benutzer keine Schreibrechte für die Datei hat. Lassen Sie uns versuchen, sie zu löschen:
[user@localhost ]$ sudo rm 123.txt
[user@localhost ]$ ls 123.txt
ls: cannot access 123.txt: No such file or directory
Wohin werden die Daten geschrieben? Und werden sie überhaupt geschrieben? Überprüfen wir:
[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)
Ja, unser Dateideskriptor existiert noch, und wir können mit diesem Dateideskriptor wie mit unserer alten Datei arbeiten; wir können ihn lesen, leeren und kopieren.
Wir schauen uns die Dateigröße an:
[user@localhost ]$ lsof | grep 123.txt
python 31083 user 3w REG 8,5 19923457 2621522 \/home\/user\/123.txt
Die Dateigröße beträgt 19923457. Wir versuchen, die Datei zu leeren:
[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
Wie wir sehen, wächst die Dateigröße nur, und unser Truncate-Befehl hat nicht funktioniert. Lassen Sie uns die Dokumentation zum Systemaufruf konsultieren. . Wenn wir beim Öffnen der Datei das Flag O_APPEND verwenden, überprüft das Betriebssystem bei jedem Schreiben die Dateigröße und schreibt die Daten am Ende der Datei, und dies geschieht atomar. Dies ermöglicht mehreren Threads oder Prozessen, in dieselbe Datei zu schreiben. Aber in unserem Code verwenden wir dieses Flag nicht. Wir können eine andere Dateigröße in lsof nach dem Truncate sehen, nur wenn wir die Datei zum Anhängen öffnen, was bedeutet, dass wir in unserem Code anstelle von
with open("123.txt", "w") as f:
wir
with open("123.txt", "a") as f:
Überprüfen mit dem „w“-Flag
[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
und mit dem 'a'-Flag
[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
Ein bereits laufender Prozess wird programmiert
Oft verwenden Programmierer beim Erstellen und Testen von Programmen Debugger (zum Beispiel GDB) oder verschiedene Protokollierungslevels in der Anwendung. Linux ermöglicht es, ein bereits laufendes Programm tatsächlich zu ändern, beispielsweise Variablenwerte zu ändern, Breakpoints zu setzen usw.
Um zur ursprünglichen Frage zurückzukehren, bei der es an Speicherplatz für das Schreiben einer Datei mangelt, versuchen wir, das Problem zu simulieren.
Wir erstellen eine Datei für unseren Bereich, die wir als separates Laufwerk einhängen werden:
[user@localhost ~]$ dd if=/dev/zero of=~/tempfile_for_article.dd bs=1M count=10
10+0 Datensätze eingelesen
10+0 Datensätze geschrieben
10485760 Bytes (10 MB) kopiert, 0.00525929 s, 2.0 GB/s
[user@localhost ~]$
Wir erstellen ein Dateisystem:
[user@localhost ~]$ mkfs.ext4 ~/tempfile_for_article.dd
mke2fs 1.42.9 (28-Dec-2013)
/home/user/tempfile_for_article.dd ist kein Block-Sondergerät.
Weiter machen? (y,n) y
...
Superblock und Dateisystembuchhaltungsinformationen werden geschrieben: abgeschlossen
[user@localhost ~]$
Wir hängen das Dateisystem ein:
[user@localhost ~]$ sudo mount ~/tempfile_for_article.dd /mnt/
[sudo] Passwort für Benutzer:
[user@localhost ~]$ df -h | grep mnt
/dev/loop0 8.7M 172K 7.9M 3% /mnt
Erstellen Sie ein Verzeichnis mit unserem Eigentümer:
[user@localhost ~]$ sudo mkdir /mnt/logs
[user@localhost ~]$ sudo chown user: /mnt/logs
Öffnen Sie die Datei nur zum Schreiben in unserem Programm:
with open("/mnt/logs/123.txt", "w") as f:
Starten wir
[user@localhost ]$ python openforwrite.py
Wir warten ein paar Sekunden
[user@localhost ~]$ df -h | grep mnt
/dev/loop0 8.7M 8.0M 0 100% /mnt
Also haben wir das Problem, das zu Beginn dieses Artikels beschrieben wurde. Freier Speicherplatz 0, belegt 100%.
Wir erinnern uns, dass wir nach den Bedingungen der Aufgabe versuchen, sehr wichtige Daten zu speichern, die nicht verloren gehen dürfen. Und gleichzeitig müssen wir den Dienst reparieren, ohne den Prozess neu zu starten.
Angenommen, wir haben dennoch Platz auf der Festplatte, aber in einer anderen Partition, zum Beispiel in /home.
Versuchen wir, unseren Code „on-the-fly“ umzuprogrammieren.
Wir sehen die PID unseres Prozesses, der den gesamten Speicherplatz verbraucht hat:
[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
Wir verbinden uns mit dem Prozess über gdb
[user@localhost ~]$ gdb -p 10078
...
(gdb)
Wir sehen die offenen Dateideskriptoren:
(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
Wir betrachten die Informationen über den Dateideskriptor mit der Nummer 3, der uns interessiert.
(gdb) shell cat /proc/10078/fdinfo/3
pos: 8189952
flags: 0100001
mnt_id: 482
In Anbetracht des Systemaufrufs, den Python macht (siehe oben, wo wir strace ausgeführt und den open-Aufruf gefunden haben), um unseren Code zur Öffnung einer Datei zu verarbeiten, machen wir dasselbe selbst im Namen unseres Prozesses, aber die O_WRONLY|O_CREAT|O_TRUNC-Bits müssen durch einen numerischen Wert ersetzt werden. Dazu öffnen wir den Quellcode des Kernels, zum Beispiel, und sehen uns an, wofür die Flags verantwortlich sind.
#define O_WRONLY 00000001
#define O_CREAT 00000100
#define O_TRUNC 00001000
Wir kombinieren alle Werte zu einem, erhalten 00001101.
Wir führen unseren Aufruf aus gdb aus.
(gdb) call open("/home/user/123.txt", 00001101,0666)
$1 = 4
Also haben wir einen neuen Dateideskriptor mit der Nummer 4 und eine neue geöffnete Datei auf einem anderen Laufwerk, überprüfen wir:
(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user 0 Okt 8 11:06 .
dr-xr-xr-x 9 user user 0 Okt 8 11:06 ..
lrwx------ 1 user user 64 Okt 8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Okt 8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Okt 8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Okt 8 11:09 3 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 Okt 8 11:15 4 -> /home/user/123.txt
Wir erinnern uns an das Beispiel mit Pipe – wie Bash die Dateideskriptoren ändert, und haben bereits den Systemaufruf dup2 gelernt.
Wir versuchen, einen Dateideskriptor mit einem anderen zu ersetzen.
(gdb) call dup2(4,3)
$2 = 3
Überprüfen wir:
(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user 0 Okt 8 11:06 .
dr-xr-xr-x 9 user user 0 Okt 8 11:06 ..
lrwx------ 1 user user 64 Okt 8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Okt 8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Okt 8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Okt 8 11:09 3 -> /home/user/123.txt
l-wx------ 1 user user 64 Okt 8 11:15 4 -> /home/user/123.txt
Wir schließen den Dateideskriptor 4, da wir ihn nicht benötigen:
(gdb) call close(4)
$1 = 0
Und wir beenden gdb.
(gdb) quit
Eine Debugging-Sitzung ist aktiv.
Inferior 1 [Prozess 10078] wird abgetrennt.
Möchten Sie trotzdem beenden? (y oder n) y
Von Programm abtrennen: /usr/bin/python2.7, Prozess 10078
Wir überprüfen die neue Datei:
[user@localhost ~]$ ls -lah /home/user/123.txt
-rw-rw-r-- 1 user user 5.1M Okt 8 11:18 /home/user/123.txt
[user@localhost ~]$ ls -lah /home/user/123.txt
-rw-rw-r-- 1 user user 7.1M Okt 8 11:18 /home/user/123.txt
Wie wir sehen, werden die Daten in die neue Datei geschrieben. Lassen Sie uns die alte überprüfen:
[user@localhost ~]$ ls -lah /mnt/logs/123.txt
-rw-rw-r-- 1 user user 7.9M Okt 8 11:08 /mnt/logs/123.txt
Daten sind nicht verloren, die Anwendung funktioniert, die Protokolle werden an einen neuen Ort geschrieben.
Lassen Sie uns die Aufgabe etwas komplizierter gestalten.
Angenommen, die Daten sind uns wichtig, aber wir haben auf keiner Partition freien Speicherplatz und können keine Festplatte anschließen.
Was wir tun können, ist, unsere Daten irgendwohin umzuleiten, beispielsweise in ein Pipe, und die Daten aus dem Pipe wiederum über ein Programm, wie netcat, ins Netzwerk weiterzuleiten.
Wir können ein benanntes Pipe mit dem Befehl mkfifo erstellen. Es wird eine Pseudo-Datei im Dateisystem erstellen, selbst wenn kein freier Speicherplatz darauf verfügbar ist.
Wir starten die Anwendung neu und überprüfen:
[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
Es gibt keinen Speicherplatz auf der Festplatte, aber wir erstellen erfolgreich ein benanntes Pipe dort:
[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
Jetzt müssen wir alle Daten, die durch dieses Pipe auf einen anderen Server über das Netzwerk gelangen, irgendwie verpacken. Dafür verwenden wir das gleiche netcat.
Auf dem Server remote-server.example.com starten wir
[user@localhost ~]$ nc -l 7777 > 123.txt
Auf unserem Problemserver starten wir in einem separaten Terminal
[user@localhost ~]$ nc remote-server.example.com 7777 < /mnt/logs/megapipe
Nun gelangen alle Daten, die in das Pipe geschrieben werden, automatisch auf stdin in netcat, das sie über das Netzwerk an Port 7777 sendet.
Alles, was wir jetzt noch tun müssen, ist, unsere Daten in dieses benannte Pipe zu schreiben.
Wir haben bereits eine laufende Anwendung:
[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
Von all den Flags benötigen wir nur O_WRONLY, da die Datei bereits existiert und wir sie nicht löschen müssen.
[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
Wir überprüfen den Remote-Server remote-server.example.com
[user@localhost ~]$ ls -lah 123.txt
-rw-rw-r-- 1 user user 38M Oct 8 14:21 123.txt
Daten werden übertragen, wir prüfen den problematischen Server
[user@localhost ~]$ ls -lah /mnt/logs/
total 7.9M
drwxr-xr-x 2 user user 1.0K Okt 8 11:28 .
drwxr-xr-x 4 root root 1.0K Okt 8 10:55 ..
-rw-rw-r-- 1 user user 7.9M Okt 8 14:17 123.txt
prw-rw-r-- 1 user user 0 Okt 8 14:22 megapipe
Die Daten wurden gespeichert, das Problem ist gelöst.
Ich nutze die Gelegenheit, um Grüße an die Kollegen von Degiro zu senden.
Hört die Podcasts von Radio-T.
Allen alles Gute.
Als Hausaufgabe schlage ich vor, darüber nachzudenken, was in den Dateideskriptoren von cat und sleep stehen wird, wenn man diesen Befehl ausführt:
[user@localhost ~]$ cat /dev/zero 2>/dev/null| sleep 10000
Quelle: habr.com
