Dateideskriptor unter Linux mit Beispielen

Während eines Interviews wurde ich einmal gefragt: Was würden Sie tun, wenn Sie feststellen, dass ein Dienst nicht funktioniert, weil auf der Festplatte nicht mehr genügend Speicherplatz vorhanden ist?

Natürlich antwortete ich, dass ich sehen würde, was an diesem Ort bewohnt sei, und wenn möglich würde ich den Ort säubern.
Dann fragte der Interviewer: Was ist, wenn auf der Partition kein freier Speicherplatz vorhanden ist, Sie aber auch keine Dateien sehen, die den gesamten Speicherplatz belegen würden?

Dazu habe ich gesagt, dass Sie sich jederzeit die Deskriptoren geöffneter Dateien ansehen können, beispielsweise mit dem Befehl lsof, und nachvollziehen können, welche Anwendung den gesamten verfügbaren Speicherplatz belegt hat, und dann entsprechend den Umständen handeln können, je nachdem, ob die Daten benötigt werden .

Der Interviewer unterbrach mich beim letzten Wort und fügte zu seiner Frage hinzu: „Angenommen, wir brauchen die Daten nicht, es ist nur ein Debug-Protokoll, aber die Anwendung funktioniert nicht, weil sie kein Debug schreiben kann“?

„Okay“, antwortete ich, „wir können das Debuggen in der Anwendungskonfiguration deaktivieren und neu starten.“
Der Interviewer wandte ein: „Nein, wir können die Anwendung nicht neu starten, wir haben immer noch wichtige Daten im Speicher und wichtige Clients sind mit dem Dienst selbst verbunden, den wir nicht zwingen können, die Verbindung erneut herzustellen.“

„Okay“, 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 ls-Befehl nicht sehen.“ auf dem Dateisystem.“

Der Interviewer war zufrieden, ich jedoch nicht.

Dann dachte ich: Warum gräbt die Person, die mein Wissen testet, nicht tiefer? Was aber, wenn die Daten doch wichtig sind? Was passiert, wenn wir einen Prozess nicht neu starten können und der Prozess in das Dateisystem auf einer Partition schreibt, die keinen freien Speicherplatz hat? Was wäre, wenn wir nicht nur die Daten verlieren könnten, die bereits geschrieben wurden, sondern auch die Daten, die dieser Prozess schreibt oder zu schreiben versucht?

Tuzik

Zu Beginn meiner Karriere habe ich versucht, eine kleine Anwendung zu erstellen, die Benutzerinformationen speichern musste. Und dann dachte ich, wie kann ich den Benutzer seinen Daten zuordnen? Ich habe zum Beispiel Ivanov Ivan Ivanovich und er hat einige Informationen, aber wie kann ich mich mit ihnen anfreunden? Ich kann direkt darauf hinweisen, dass der Hund namens „Tuzik“ genau diesem Ivan gehört. Was aber, wenn er seinen Namen ändert und statt Ivan beispielsweise Olya wird? Dann wird sich herausstellen, dass unsere Olya Ivanovna Ivanova keinen Hund mehr haben wird und unser Tuzik immer noch dem nicht existierenden Ivan gehören wird. Eine Datenbank, die jedem Benutzer eine eindeutige Kennung (ID) gab, half bei der Lösung dieses Problems, und mein Tuzik war an diese ID gebunden, die eigentlich nur eine Seriennummer war. Der Besitzer des Asses hatte also die ID-Nummer 2, und irgendwann war Ivan unter dieser ID, und dann wurde Olya unter derselben ID. Das Problem der Menschlichkeit und Tierhaltung wurde praktisch gelöst.

Dateideskriptor

Das Problem der Datei und des Programms, das mit dieser Datei arbeitet, ist ungefähr das gleiche wie das unseres Hundes und unseres Menschen. Angenommen, ich habe eine Datei namens ivan.txt geöffnet und angefangen, das Wort tuzik hineinzuschreiben, habe es aber nur geschafft, den ersten Buchstaben „t“ in die Datei zu schreiben, und diese Datei wurde von jemandem beispielsweise in olya.txt umbenannt. Aber die Datei bleibt dieselbe und ich möchte mein Ass immer noch darin aufzeichnen. Jedes Mal, wenn eine Datei durch einen Systemaufruf geöffnet wird XNUMXh geöffnet In jeder Programmiersprache erhalte ich eine eindeutige ID, die mich auf eine Datei verweist. Diese ID ist der Dateideskriptor. Und es spielt überhaupt keine Rolle, was und wer als nächstes mit dieser Datei macht, sie kann gelöscht werden, sie kann umbenannt werden, der Eigentümer kann geändert werden oder die Lese- und Schreibrechte können entzogen werden, ich habe weiterhin Zugriff weil ich zum Zeitpunkt des Öffnens der Datei das Recht hatte, sie zu lesen und/oder zu schreiben, und es mir gelang, damit zu arbeiten, was bedeutet, dass ich dies auch weiterhin tun muss.

Unter Linux öffnet die libc-Bibliothek drei Deskriptordateien für jede laufende Anwendung (Prozess), nummeriert mit 3. Weitere Informationen finden Sie unter den Links Mann stdio и Mann stdout

  • Der Dateideskriptor 0 heißt STDIN und ist mit der Anwendungseingabe verknüpft
  • Der Dateideskriptor 1 heißt STDOUT und wird von Anwendungen zur Ausgabe von Daten, beispielsweise Druckbefehlen, verwendet
  • Der Dateideskriptor 2 heißt STDERR und wird von Anwendungen zur Ausgabe von Fehlermeldungen verwendet.

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

Die Liste der Dateideskriptoren kann für jeden Prozess angezeigt werden, wenn Sie dessen PID kennen.

Öffnen wir beispielsweise die Bash-Konsole und sehen uns die PID unseres Prozesses an

[user@localhost ]$ echo $$
15771

Lassen Sie uns in der zweiten Konsole ausführen

[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 Nummer 255 für die Zwecke dieses Artikels getrost ignorieren; er wurde für seine Zwecke von Bash selbst und nicht von der verknüpften Bibliothek geöffnet.

Jetzt sind alle drei Deskriptordateien dem Pseudo-Endgerät zugeordnet /dev/pts, aber wir können sie trotzdem manipulieren, zum Beispiel in einer 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

Umleiten und weiterleiten

Sie können diese drei Deskriptordateien problemlos in jedem Prozess überschreiben, auch in der Bash, beispielsweise durch eine Pipe, die zwei Prozesse verbindet, siehe

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

Sie können diesen Befehl selbst ausführen strace -f und schauen Sie, was im Inneren vor sich geht, aber ich werde es Ihnen kurz sagen.

Unser übergeordneter Bash-Prozess mit PID 15771 analysiert unseren Befehl und versteht genau, wie viele Befehle wir ausführen möchten. In unserem Fall sind es zwei davon: cat und sleep. Bash weiß, dass es zwei untergeordnete Prozesse erstellen und diese in einer Pipe zusammenführen muss. Insgesamt benötigt Bash zwei untergeordnete Prozesse und eine Pipe.

Bash führt einen Systemaufruf aus, bevor es untergeordnete Prozesse erstellt Rohr und empfängt neue Dateideskriptoren im temporären Pipe-Puffer, aber dieser Puffer verbindet unsere beiden untergeordneten Prozesse noch nicht.

Für den übergeordneten Prozess sieht es so aus, als gäbe es bereits eine Pipe, aber 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 mit dem Systemaufruf klonen Bash erstellt 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 clone den Prozess zusammen mit allen Dateideskriptoren klont, sodass diese im übergeordneten Prozess und in den untergeordneten Prozessen gleich sind. Die Aufgabe des übergeordneten Prozesses mit der PID 15771 besteht darin, die untergeordneten Prozesse zu überwachen, sodass er lediglich auf eine Antwort der untergeordneten Prozesse wartet.

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

Im ersten untergeordneten Bash-Prozess mit PID 9004, dem Systemaufruf dup2, ändert unseren STDOUT-Dateideskriptor Nummer 1 in einen Dateideskriptor, der auf Pipe zeigt, in unserem Fall ist es Nummer 3. Somit landet alles, was der erste untergeordnete Prozess mit PID 9004 nach STDOUT schreibt, automatisch im Pipe-Puffer.

Im zweiten untergeordneten Prozess mit PID 9005 verwendet Bash dup2, um den Dateideskriptor STDIN Nummer 0 zu ändern. Jetzt wird alles, was unser zweiter Bash mit PID 9005 liest, aus der Pipe gelesen.

Danach werden auch die Dateideskriptoren mit den Nummern 3 und 4 in den Kindprozessen geschlossen, da sie nicht mehr verwendet werden.

Den Dateideskriptor 255 ignoriere ich bewusst; er wird für interne Zwecke von Bash selbst verwendet und wird auch in untergeordneten Prozessen geschlossen.

Als nächstes beginnt Bash im ersten untergeordneten Prozess mit PID 9004 mit der Verwendung eines Systemaufrufs exec die ausführbare Datei, die wir in der Befehlszeile angegeben haben, in unserem Fall ist es /usr/bin/cat.

Im zweiten untergeordneten Prozess mit PID 9005 führt Bash die zweite von uns angegebene ausführbare Datei aus, in unserem Fall /usr/bin/sleep.

Der exec-Systemaufruf schließt keine Dateihandles, es sei denn, sie wurden zum Zeitpunkt des Öffnungsaufrufs mit dem Flag O_CLOEXEC geöffnet. In unserem Fall werden nach dem Starten der ausführbaren Dateien alle aktuellen Dateideskriptoren gespeichert.

Checken Sie in der Konsole ein:

[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, ist die eindeutige Nummer unserer Pfeife in beiden Verfahren gleich. Wir haben also eine Verbindung zwischen zwei verschiedenen Prozessen mit demselben übergeordneten Element.

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 intern passiert, zum Beispiel so:

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

Kehren wir zu unserem Problem mit wenig Speicherplatz und dem Versuch zurück, 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 außerdem aus irgendeinem Grund keine Daten auf die Festplatte schreiben konnten, ignorieren wir dies einfach und versuchen in einer Sekunde erneut, die 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

Lassen Sie uns das Programm ausführen und uns 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 Sie sehen, haben wir unsere drei Standard-Dateideskriptoren und einen weiteren, den wir geöffnet haben. Lassen Sie uns die Dateigröße überprüfen:

[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 noch geschrieben werden, obwohl unser Benutzer keine Berechtigung zum Schreiben in die Datei hat. 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 werden die Daten geschrieben? Und sind sie überhaupt geschrieben? Wir überprü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, unser Dateideskriptor existiert noch und wir können diesen Dateideskriptor wie unsere alte Datei behandeln, wir können ihn lesen, löschen und kopieren.

Schauen wir 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. Versuchen wir, 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, nimmt die Dateigröße nur zu und unser Trunk hat nicht funktioniert. Schauen wir uns die Dokumentation zum Systemaufruf an XNUMXh geöffnet. Wenn wir beim Öffnen einer Datei das Flag O_APPEND verwenden, überprüft das Betriebssystem bei jedem Schreibvorgang die Dateigröße und schreibt Daten bis zum Ende der Datei, und zwar atomar. Dadurch können mehrere Threads oder Prozesse in dieselbe Datei schreiben. Aber in unserem Code verwenden wir dieses Flag nicht. Wir können eine andere Dateigröße in lsof nach Trunk nur sehen, wenn wir die Datei zum zusätzlichen Schreiben öffnen, also stattdessen in unserem Code

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

wir müssen setzen

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

Überprüfung mit der „w“-Flagge

[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 „a“-Flagge

[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 eines bereits laufenden Prozesses

Beim Erstellen und Testen von Programmen verwenden Programmierer häufig 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, beispielsweise die Werte von Variablen zu ändern, einen Haltepunkt festzulegen usw. usw.

Zurück zur ursprünglichen Frage, dass nicht genügend Speicherplatz zum Schreiben einer Datei vorhanden ist, versuchen wir, das Problem zu simulieren.

Erstellen wir 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 ~]$

Lassen Sie uns ein Dateisystem erstellen:

[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

Wir erstellen ein Verzeichnis mit unserem Besitzer:

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

Öffnen wir die Datei nur zum Schreiben in unserem Programm:

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

Rennen

[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

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

Wir erinnern uns daran, dass wir je nach den Bedingungen der Aufgabe versuchen, sehr wichtige Daten aufzuzeichnen, die nicht verloren gehen dürfen. Und gleichzeitig müssen wir den Dienst reparieren, ohne den Prozess neu zu starten.

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

Versuchen wir, unseren Code „im laufenden Betrieb neu zu programmieren“.

Schauen wir uns die PID unseres Prozesses an, die den gesamten Speicherplatz verschlungen 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

Stellen Sie über gdb eine Verbindung zum Prozess her

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

Schauen wir uns die Deskriptoren der geöffneten Dateien 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 schauen uns die Informationen zum Dateideskriptor Nummer 3 an, die uns interessieren

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

Wenn wir bedenken, welchen Systemaufruf Python ausführt (siehe oben, wo wir strace ausgeführt und den Aufruf open gefunden haben), machen wir bei der Verarbeitung unseres Codes zum Öffnen einer Datei dasselbe selbst im Namen unseres Prozesses, benötigen jedoch O_WRONLY|O_CREAT| O_TRUNC-Bits werden durch einen numerischen Wert ersetzt. Öffnen Sie dazu beispielsweise die Kernel-Quellen hier und schauen Sie sich an, welche Flags wofür verantwortlich sind

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

Wir kombinieren alle Werte zu einem, wir erhalten 00001101

Wir führen unseren Anruf von gdb aus

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

Wir haben also einen neuen Dateideskriptor mit der Nummer 4 und eine neue geöffnete Datei auf einer anderen Partition erhalten. Wir überprü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 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 Oct  8 11:15 4 -> /home/user/123.txt

Wir erinnern uns an das Beispiel mit Pipe – wie Bash Dateideskriptoren ändert, und wir haben bereits den Systemaufruf dup2 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 beenden Sie gdb

(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üfung der neuen 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, werden die Daten in eine neue Datei geschrieben. Schauen wir uns die alte an:

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

Es gehen keine Daten verloren, die Anwendung funktioniert, Protokolle werden an einen neuen Speicherort geschrieben.

Machen wir die Aufgabe etwas komplizierter

Stellen wir uns vor, dass die Daten für uns wichtig sind, wir aber in keiner der Partitionen Speicherplatz haben und die Festplatte nicht anschließen können.

Was wir tun können, ist, unsere Daten irgendwohin umzuleiten, zum Beispiel in eine Pipe, und wiederum Daten von der Pipe über ein Programm, zum Beispiel Netcat, in das Netzwerk umzuleiten.
Mit dem Befehl mkfifo können wir eine Named Pipe erstellen. Es wird eine Pseudodatei im Dateisystem erstellt, auch wenn darauf kein freier Speicherplatz vorhanden ist.

Starten Sie die Anwendung neu und prüfen Sie:

[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 ist kein Speicherplatz vorhanden, 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 alle Daten, die in diese Pipe gelangen, irgendwie über das Netzwerk an einen anderen Server weiterleiten; dafür ist derselbe Netcat geeignet.

Auf dem Server remote-server.example.com führen wir aus

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

Auf unserem problematischen Server starten wir in einem separaten Terminal

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

Jetzt werden alle Daten, die in der Pipe landen, automatisch an stdin in Netcat gesendet, das sie an Port 7777 an das Netzwerk sendet.

Alles, was wir tun müssen, ist, unsere Daten in diese Named Pipe zu schreiben.

Wir haben die Anwendung bereits ausgeführt:

[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 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

Ü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

Die Daten kommen, wir überprüfen den 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

Die Daten sind gespeichert, das Problem ist gelöst.

Ich nutze diese Gelegenheit, um meinen Kollegen von Degiro Hallo zu sagen.
Hören Sie Radio-T-Podcasts.

Всем добра

Als Hausaufgabe schlage ich vor, dass Sie darüber nachdenken, was in den Prozessdateideskriptoren cat und sleep enthalten sein wird, wenn Sie den folgenden Befehl ausführen:

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

Source: habr.com

Kommentar hinzufügen