Linux-Deskriptordatei mit Beispielen

Einmal haben sie mich in einem Interview gefragt, was Sie tun sollen, wenn Sie einen Dienst finden, der nicht funktioniert, weil auf der Festplatte nicht genügend Speicherplatz vorhanden ist.

Natürlich antwortete ich, dass ich sehen würde, was dieser Ort besetzt ist, und wenn möglich, würde ich den Ort reinigen.
Dann fragte der Interviewer, was ist, wenn in der Sektion kein freier Speicherplatz vorhanden ist, Sie aber auch nicht die Dateien sehen, die den gesamten Platz einnehmen würden?

Dazu habe ich gesagt, dass Sie immer offene Dateideskriptoren anzeigen können, beispielsweise mit dem Befehl lsof, und verstehen können, welche Anwendung den gesamten verfügbaren Speicherplatz belegt hat, und dann je nach den Umständen fortfahren können, je nachdem, ob die Daten benötigt werden.

Der Interviewer unterbrach mich beim letzten Wort und fügte meine Frage hinzu: "Angenommen, wir benötigen die Daten nicht, es ist nur ein Debug-Protokoll, aber die Anwendung funktioniert nicht, weil sie kein Debug aufzeichnen kann."

"Okay", antwortete ich, "wir können das Debuggen in der Konfiguration der Anwendung deaktivieren und neu starten."
Der Interviewer beanstandete: „Nein, wir können die Anwendung nicht neu starten, wichtige Daten werden noch in unserem Speicher gespeichert und wichtige Clients sind mit dem Dienst selbst verbunden, den wir nicht erzwingen können, um die Verbindung wiederherzustellen.“

"Nun", sagte ich, "wenn wir die Anwendung nicht neu starten können und die Daten für uns nicht wichtig sind, können wir diese geöffnete Datei einfach über den Dateideskriptor löschen, auch wenn wir sie im Befehl ls im Dateisystem nicht sehen."

Der Interviewer war zufrieden, ich aber nicht.

Dann dachte ich, warum gräbt die Person, die mein Wissen testet, nicht tiefer? Was aber, wenn die Daten noch wichtig sind? Was ist, wenn wir den Prozess nicht neu starten können und dieser Prozess gleichzeitig in einem Abschnitt, in dem kein freier Speicherplatz vorhanden ist, in das Dateisystem schreibt? Was ist, wenn wir nicht nur nicht bereits aufgezeichnete Daten verlieren können, sondern auch die Daten, die dieser Prozess schreibt oder aufzuzeichnen versucht?

Tuzik


Zu Beginn meiner Karriere habe ich versucht, eine kleine Anwendung zu erstellen, in der Informationen über Benutzer gespeichert werden mussten. Und dann dachte ich, wie kann ich den Benutzer seinen Daten zuordnen. Zum Beispiel habe ich Ivan Ivanov Ivanich, und er hat einige Daten, aber wie kann man sich mit ihnen anfreunden? Ich kann direkt darauf hinweisen, dass der Hund namens "Tuzik" genau diesem Ivan gehört. Aber was ist, wenn er seinen Namen ändert und anstelle von Ivan zum Beispiel Olya wird? Dann stellt sich heraus, dass unsere Olya Ivanovna Ivanova keinen Hund mehr haben wird und unser Tuzik immer noch dem nicht existierenden Ivan gehören wird. Diese Datenbank wurde durch eine Datenbank unterstützt, die jedem Benutzer eine eindeutige Kennung (ID) gab, und mein Tuzik wurde an diese ID angehängt, die eigentlich nur eine Ordnungszahl war. Somit hatte der Besitzer des Tuzik die ID-Nummer 2, und irgendwann befand sich Ivan unter dieser ID, und dann wurde Olya dieselbe ID. Das Problem der Menschlichkeit und Tierhaltung ist praktisch gelöst.

Dateideskriptor


Das Problem der Datei und des Programms, die mit dieser Datei arbeiten, ist ungefähr das gleiche wie das unseres Hundes und unserer Person. Angenommen, ich habe eine Datei unter dem Namen ivan.txt geöffnet und mit dem Schreiben des Wortes tuzik begonnen, aber es ist mir gelungen, nur den ersten Buchstaben „t“ in die Datei zu schreiben, und diese Datei wurde von jemandem umbenannt, z. B. olya.txt. Aber die Datei ist dieselbe geblieben, und ich möchte immer noch mein Ass darin schreiben. Jedes Mal, wenn ich eine Datei mit dem offenen Systemaufruf in einer beliebigen Programmiersprache öffne, erhalte ich eine eindeutige ID, die mich auf eine Datei verweist. Diese ID ist ein Dateideskriptor. Und es spielt keine Rolle, was jemand als Nächstes mit dieser Datei macht, er kann sie löschen, umbenennen, den Eigentümer ändern oder Lese- und Schreibberechtigungen wegnehmen. Ich habe weiterhin Zugriff darauf, da zum Zeitpunkt des Öffnens der Datei Ich hatte das Recht, es zu lesen und / oder zu schreiben, und es gelang mir, mit ihm zusammenzuarbeiten, was bedeutet, dass ich dies weiterhin tun muss.

Unter Linux wird die libc-Bibliothek für jede laufende Anwendungs- (Prozess-) 3-Deskriptordatei mit den Nummern 0,1,2 geöffnet. Weitere Informationen finden Sie unter den Links man stdio und man stdout

  • Der Dateideskriptor 0 heißt STDIN und ist der Dateneingabe aus der Anwendung zugeordnet
  • Der Dateideskriptor 1 heißt STDOUT und wird von Anwendungen verwendet, um Daten auszugeben, beispielsweise Druckbefehle
  • Der Dateideskriptor 2 heißt STDERR und wird von Anwendungen zur Ausgabe von Fehlerberichtsdaten verwendet.

Wenn Sie in Ihrem Programm eine Datei zum Lesen oder Schreiben öffnen, erhalten Sie höchstwahrscheinlich die erste freie ID und dies ist Nummer 3.

Eine Liste von Deskriptordateien kann von jedem Prozess aus angezeigt werden, wenn Sie die PID kennen.

Öffnen Sie beispielsweise eine Konsole mit Bash und sehen Sie die PID unseres Prozesses

[user@localhost ]$ echo $$ 15771 

Führen Sie in der zweiten Konsole aus

 [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 

Sie können den Dateideskriptor Nr. 255 im Rahmen dieses Artikels ignorieren. Er wurde von bash selbst und nicht von einer verknüpften Bibliothek für Ihre Anforderungen geöffnet.

Jetzt sind alle 3 Deskriptordateien dem Pseudo-Endgerät / dev / pts zugeordnet , aber wir können sie weiterhin bearbeiten, z. B. in der zweiten Konsole ausführen

 [user@localhost ]$ echo "hello world" > /proc/15771/fd/0 

Und in der ersten Konsole werden wir sehen

 [user@localhost ]$ hello world 

Weiterleiten und leiten


Sie können diese 3 Deskriptordateien in jedem Prozess leicht überschreiben, auch in Bash, z. B. über eine Pipe, die zwei Prozesse verbindet

 [user@localhost ]$ cat /dev/zero | sleep 10000 

Sie können diesen Befehl selbst mit strace -f ausführen und sehen, was im Inneren passiert, aber ich werde es Ihnen kurz sagen.

Unsere übergeordnete Prozess-Bash mit PID 15771 analysiert unseren Befehl und versteht genau, wie viele Befehle wir ausführen möchten. In unserem Fall gibt es zwei davon: Katze und Schlaf. Bash weiß, dass er zwei untergeordnete Prozesse erstellen und mit einer Pipe kombinieren muss. Total Bash benötigt 2 untergeordnete Prozesse und eine Pipe.

Vor dem Erstellen untergeordneter Prozesse startet bash den Pipe -Systemaufruf und empfängt neue Dateideskriptoren für den temporären Pipe-Puffer. Dieser Puffer bindet unsere beiden untergeordneten Prozesse jedoch noch nicht.

Für den übergeordneten Prozess scheint die Pipe bereits vorhanden zu sein, und es gibt noch keine untergeordneten Prozesse:

 PID command 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 

Dann erstellt Clone Bash mithilfe eines Systemaufrufs zwei untergeordnete Prozesse, und unsere drei Prozesse sehen folgendermaßen aus:

 PID command 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 command 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 command 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 

Vergessen Sie nicht, dass der Klon den Prozess zusammen mit allen Dateideskriptoren klont, damit sie im übergeordneten und im untergeordneten Prozess gleich sind. Die Aufgabe des übergeordneten Prozesses mit PID 15771 besteht darin, die untergeordneten Prozesse zu überwachen, sodass nur auf eine Antwort der untergeordneten Prozesse gewartet wird.

Daher benötigt er keine Pipe und schließt die Dateideskriptoren mit den Nummern 3 und 4.

Im ersten untergeordneten Bash-Prozess mit PID 9004, dem Systemaufruf dup2 , wird unser STDOUT-Dateideskriptor mit der Nummer 1 in einen Dateideskriptor geändert, der auf die Pipe zeigt. In unserem Fall ist es die Nummer 3. Somit wird alles, was der erste untergeordnete Prozess mit der PID 9004 in STDOUT schreibt fällt automatisch in den Rohrpuffer.

Im zweiten untergeordneten Prozess mit PID 9005 ändert bash den Deskriptor STDIN mit der Nummer 0 unter Verwendung von dup2. Jetzt liest alles, was unser zweiter bash mit PID 9005 liest, aus der Pipe.

Danach werden die Deskriptoren mit den Nummern 3 und 4 auch in untergeordneten Prozessen geschlossen, da sie nicht mehr verwendet werden.

Ich ignoriere absichtlich den Dateideskriptor 255, er verwendet Bash für interne Anforderungen und wird auch in untergeordneten Prozessen geschlossen.

Außerdem startet bash im ersten untergeordneten Prozess mit PID 9004 die ausführbare Datei, die wir in der Befehlszeile mit dem Systemaufruf exec angegeben haben , in unserem Fall / usr / bin / cat.

Im zweiten untergeordneten Prozess mit PID 9005 startet bash die zweite ausführbare Datei, die wir angegeben haben, in unserem Fall / usr / bin / sleep.

Der Systemaufruf exec schließt keine Dateideskriptoren, wenn sie während des Aufrufs nicht mit dem Flag O_CLOEXEC geöffnet wurden. In unserem Fall werden nach dem Ausführen der ausführbaren Dateien alle aktuellen Dateideskriptoren gespeichert.

Überprüfen Sie 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 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 

Wie Sie sehen können, ist die eindeutige Anzahl unserer Rohre in beiden Prozessen gleich. Wir haben also eine Verbindung zwischen zwei verschiedenen Prozessen mit einem Elternteil.

Für diejenigen, die mit den von bash verwendeten Systemaufrufen nicht vertraut sind, empfehle ich dringend, die Befehle über strace auszuführen und zu sehen, was im Inneren beispielsweise so passiert:

 strace -s 1024 -f bash -c "ls | grep hello" 

Kehren wir zu unserem Problem zurück, bei dem der Speicherplatz knapp wird und versucht wird, Daten zu speichern, ohne den Prozess neu zu starten. Schreiben wir ein kleines Programm, das ungefähr 1 Megabyte pro Sekunde auf die Festplatte schreibt. Wenn wir aus irgendeinem Grund keine Daten auf die Festplatte schreiben konnten, ignorieren wir sie einfach und versuchen nach einer Sekunde erneut, Daten zu schreiben. In dem Beispiel, in dem ich Python verwende, können Sie 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 

Führen Sie das Programm aus und sehen Sie sich die Dateideskriptoren an

 [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 Sie sehen können, haben wir unsere 3 Standard-Dateideskriptoren und einen weiteren, den wir geöffnet haben. Überprüfen Sie die Dateigröße:

 [user@localhost ]$ ls -lah 123.txt -rw-rw-r-- 1 user user 117M Oct 7 16:30 123.txt 

Daten werden geschrieben. Versuchen Sie, die Dateiberechtigungen 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 noch geschrieben werden, obwohl unser Benutzer nicht das Recht hat, in die Datei zu schreiben. Versuchen wir es zu entfernen:

 [user@localhost ]$ sudo rm 123.txt [user@localhost ]$ ls 123.txt ls: cannot access 123.txt: No such file or directory 

Wo sind die Daten geschrieben? Und sind sie überhaupt geschrieben? Wir prüfen:

 [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, unsere Deskriptordatei ist noch vorhanden, und wir können mit dieser Deskriptordatei wie mit unserer alten Datei arbeiten, sie lesen, bereinigen und kopieren.

Wir betrachten die Dateigröße:

 [user@localhost ]$ lsof | grep 123.txt python 31083 user 3w REG 8,5 19923457 2621522 /home/user/123.txt 

Dateigröße 19923457. Versuch, die Datei zu löschen:

 [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 Sie sehen können, nimmt die Dateigröße nur zu und unser Trankate hat nicht funktioniert. Informationen zum Öffnen des Systemaufrufs finden Sie in der Dokumentation. Wenn wir beim Öffnen einer Datei das O_APPEND-Flag verwenden, geschieht dies jedes Mal atomar, wenn das Betriebssystem die Dateigröße überprüft und Daten bis zum Ende der Datei schreibt. Dadurch können mehrere Threads oder Prozesse in dieselbe Datei schreiben. In unserem Code verwenden wir dieses Flag jedoch nicht. Wir können eine andere Dateigröße in lsof nach dem Trankieren nur sehen, wenn wir die Datei für zusätzliche Aufzeichnungen öffnen, was bedeutet, dass anstelle von in unserem Code

 with open("123.txt", "w") as f: 

wir müssen setzen

 with open("123.txt", "a") as f: 

Überprüfung mit dem 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 

und mit der Flagge "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 

Programmieren Sie einen bereits laufenden Prozess


Häufig verwenden Programmierer beim Erstellen und Testen von Programmen Debugger (z. B. GDB) oder verschiedene Protokollierungsebenen in der Anwendung. Linux bietet die Möglichkeit, ein bereits laufendes Programm tatsächlich zu schreiben und zu ändern, z. B. die Werte von Variablen zu ändern, einen Haltepunkt festzulegen usw. usw.

Zurück zur ursprünglichen Frage mit nicht genügend Speicherplatz zum Schreiben der Datei, werden wir versuchen, das Problem zu emulieren.

Erstellen Sie eine Datei für unsere Partition, die wir als separate Festplatte bereitstellen:

 [user@localhost ~]$ dd if=/dev/zero of=~/tempfile_for_article.dd bs=1M count=10 10+0 records in 10+0 records out 10485760 bytes (10 MB) copied, 0.00525929 s, 2.0 GB/s [user@localhost ~]$ 

Erstellen Sie ein Dateisystem:

 [user@localhost ~]$ mkfs.ext4 ~/tempfile_for_article.dd mke2fs 1.42.9 (28-Dec-2013) /home/user/tempfile_for_article.dd is not a block special device. Proceed anyway? (y,n) y ... Writing superblocks and filesystem accounting information: done [user@localhost ~]$ 

Mounten Sie das Dateisystem:

 [user@localhost ~]$ sudo mount ~/tempfile_for_article.dd /mnt/ [sudo] password for user: [user@localhost ~]$ df -h | grep mnt /dev/loop0 8.7M 172K 7.9M 3% /mnt 

Erstellen Sie ein Verzeichnis mit unserem Besitzer:

 [user@localhost ~]$ sudo mkdir /mnt/logs [user@localhost ~]$ sudo chown user: /mnt/logs 

Wir öffnen die Datei nur zum Schreiben in unserem Programm:

 with open("/mnt/logs/123.txt", "w") as f: 

Wir starten

 [user@localhost ]$ python openforwrite.py 

Ich warte ein paar Sekunden

 [user@localhost ~]$ df -h | grep mnt /dev/loop0 8.7M 8.0M 0 100% /mnt 

Wir haben also das am Anfang dieses Artikels beschriebene Problem. Freier Speicherplatz 0, zu 100% belegt.

Wir erinnern uns, dass wir gemäß den Bedingungen der Aufgabe versuchen, sehr wichtige Daten aufzuzeichnen, die nicht verloren gehen können. Gleichzeitig müssen wir den Service reparieren, ohne den Prozess neu zu starten.

Angenommen, wir haben noch Speicherplatz, aber in einer anderen Partition, zum Beispiel in / home.

Versuchen wir, unseren Code "on the fly neu zu programmieren".

Wir betrachten 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 über gdb mit dem Prozess

 [user@localhost ~]$ gdb -p 10078 ... (gdb) 

Wir schauen uns die offenen Dateideskriptoren an:

 (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 sehen uns die Informationen zum Dateideskriptor mit der Nummer 3 an, die uns interessieren

 (gdb) shell cat /proc/10078/fdinfo/3 pos: 8189952 flags: 0100001 mnt_id: 482 

Wenn wir uns daran erinnern, welche Art von Systemaufruf Python ausführt (siehe oben, wo wir strace ausgeführt und den offenen Aufruf gefunden haben), unseren Code zum Öffnen der Datei verarbeiten, tun wir dasselbe in unserem eigenen Namen, benötigen jedoch die Bits O_WRONLY | O_CREAT | O_TRUNC durch einen numerischen Wert ersetzen. Öffnen Sie dazu beispielsweise hier die Kernelquellen und sehen Sie, welche Flags für was verantwortlich sind

#define O_WRONLY 00000001
#define O_CREAT 00000100
#define O_TRUNC 00001000

Wir kombinieren alle Werte zu einem, wir erhalten 00001101

Führen Sie unseren Anruf von gdb aus

 (gdb) call open("/home/user/123.txt", 00001101,0666) $1 = 4 

Also haben wir eine neue Deskriptordatei mit der Nummer 4 und eine neue geöffnete Datei in einem anderen Abschnitt. Überprüfen Sie:

 (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 

Wir erinnern uns an das Pipe-Beispiel - wie bash Dateideskriptoren ändert und haben den Systemaufruf dup2 bereits gelernt.

Wir versuchen, einen Dateideskriptor durch einen anderen zu ersetzen

 (gdb) call dup2(4,3) $2 = 3 

Wir prüfen:

 (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 

Wir schließen den Dateideskriptor 4, da wir ihn nicht benötigen:

 (gdb) call close (4) $1 = 0 

Und gdb verlassen

 (gdb) quit A debugging session is active. Inferior 1 [process 10078] will be detached. Quit anyway? (y or n) y Detaching from program: /usr/bin/python2.7, process 10078 

Überprüfen Sie die neue Datei:

 [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 

Wie Sie sehen können, werden die Daten in eine neue Datei geschrieben, wir überprüfen die alte:

 [user@localhost ~]$ ls -lah /mnt/logs/123.txt -rw-rw-r-- 1 user user 7.9M Oct 8 11:08 /mnt/logs/123.txt 

Daten gehen nicht verloren, die Anwendung funktioniert, Protokolle werden an einen neuen Ort geschrieben.

Lassen Sie uns die Aufgabe etwas komplizieren


Stellen Sie sich vor, die Daten sind für uns wichtig, aber wir haben in keinem der Abschnitte Speicherplatz und können die Festplatte nicht verbinden.

Was wir tun können, ist, unsere Daten irgendwo umzuleiten, zum Beispiel an Pipe, und die Daten von Pipe werden wiederum über ein Programm, zum Beispiel Netcat, zum Netzwerk umgeleitet.
Mit dem Befehl mkfifo können wir eine Named Pipe erstellen. Es wird eine Pseudodatei im Dateisystem erstellt, auch wenn kein freier Speicherplatz vorhanden 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, aber wir haben dort erfolgreich eine Named Pipe erstellt:

 [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 irgendwie alle Daten, die in diese Pipe gelangen, über das Netzwerk auf einen anderen Server verpacken, da dies alles derselbe Netcat tun wird.

Führen Sie auf dem Server remote-server.example.com aus

 [user@localhost ~]$ nc -l 7777 > 123.txt 

Führen Sie auf unserem Problemserver ein separates Terminal aus

 [user@localhost ~]$ nc remote-server.example.com 7777 < /mnt/logs/megapipe 

Jetzt werden alle Daten, die in die Pipe gelangen, automatisch in netcat an stdin gesendet, wodurch sie an Port 7777 an das Netzwerk gesendet werden.

Alles was wir tun müssen, ist unsere Daten in diese Named 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 allen Flags benötigen wir nur O_WRONLY, da die Datei bereits vorhanden ist 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 

Überprüfen des Remote-Servers remote-server.example.com

 [user@localhost ~]$ ls -lah 123.txt -rw-rw-r-- 1 user user 38M Oct 8 14:21 123.txt 

Daten gehen, wir überprüfen einen Problemserver

 [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 

Daten gespeichert, Problem behoben.

Ich nutze diese Gelegenheit, um meinen Kollegen bei Degiro meine Grüße zu übermitteln.
Hören Sie Radio T-Podcasts.

Gut zu allen.

Als Hausaufgabe schlage ich vor, darüber nachzudenken, was in den Dateideskriptoren des Katzen- und Schlafprozesses enthalten sein wird, wenn Sie diesen Befehl ausführen:

 [user@localhost ~]$ cat /dev/zero 2>/dev/null| sleep 10000 

Source: https://habr.com/ru/post/de471038/


All Articles