Bestandsbeschrijving in Linux met voorbeelden

Tijdens een interview werd mij eens gevraagd: wat gaat u doen als u merkt dat een dienst niet werkt omdat er geen ruimte meer is op de schijf?

Natuurlijk antwoordde ik dat ik zou kijken wat deze plek in beslag nam en, indien mogelijk, de plek zou schoonmaken.
Toen vroeg de interviewer: wat als er geen vrije ruimte op de partitie is, maar je ook geen bestanden ziet die alle ruimte in beslag nemen?

Hierop zei ik dat je altijd naar open bestandsdescriptors kunt kijken, bijvoorbeeld met het lsof-commando, en kunt begrijpen welke applicatie alle beschikbare ruimte in beslag heeft genomen, en dan kun je handelen afhankelijk van de omstandigheden, afhankelijk van of de gegevens nodig zijn. .

De interviewer onderbrak mij bij het laatste woord en voegde aan zijn vraag toe: "Stel dat we de gegevens niet nodig hebben, het is gewoon een debug-logboek, maar de applicatie werkt niet omdat deze geen debug kan schrijven"?

“Oké,” antwoordde ik, “we kunnen debuggen in de applicatieconfiguratie uitschakelen en opnieuw opstarten.”
De interviewer wierp tegen: “Nee, we kunnen de applicatie niet opnieuw opstarten, we hebben nog steeds belangrijke gegevens in het geheugen opgeslagen en belangrijke klanten zijn verbonden met de dienst zelf, die we niet kunnen dwingen opnieuw verbinding te maken.”

“Oké,” zei ik, “als we de applicatie niet opnieuw kunnen starten en de gegevens niet belangrijk voor ons zijn, dan kunnen we dit geopende bestand eenvoudigweg wissen via de bestandsdescriptor, zelfs als we het niet zien in het ls-commando op het bestandssysteem.”

De interviewer was tevreden, maar ik niet.

Toen dacht ik: waarom graaft de persoon die mijn kennis test niet dieper? Maar wat als de data toch belangrijk zijn? Wat als we een proces niet opnieuw kunnen starten en het proces naar het bestandssysteem schrijft op een partitie die geen vrije ruimte heeft? Wat als we niet alleen de gegevens die al zijn geschreven, kunnen verliezen, maar ook de gegevens die dit proces schrijft of probeert te schrijven?

Tuzik

In het begin van mijn carrière probeerde ik een kleine applicatie te maken waarin gebruikersinformatie moest worden opgeslagen. En toen dacht ik: hoe kan ik de gebruiker aan zijn gegevens koppelen. Ik heb bijvoorbeeld Ivanov Ivan Ivanovitsj, en hij heeft wat informatie, maar hoe kan ik vriendschap met hen sluiten? Ik kan er direct op wijzen dat de hond genaamd “Tuzik” van deze Ivan is. Maar wat als hij zijn naam verandert en in plaats van Ivan bijvoorbeeld Olya wordt? Dan zal blijken dat onze Olya Ivanovna Ivanova geen hond meer zal hebben, en onze Tuzik zal nog steeds tot de niet-bestaande Ivan behoren. Een database die elke gebruiker een unieke identificatie (ID) gaf, hielp dit probleem op te lossen, en mijn Tuzik was gekoppeld aan deze ID, die in feite slechts een serienummer was. De eigenaar van de tuzik had dus ID-nummer 2, en op een gegeven moment stond Ivan onder dit ID, en vervolgens werd Olya onder hetzelfde ID. Het probleem van de mensheid en de veehouderij was praktisch opgelost.

Bestandsbeschrijving

Het probleem van het bestand en het programma dat met dit bestand werkt, is ongeveer hetzelfde als dat van onze hond en mens. Stel dat ik een bestand met de naam ivan.txt opende en het woord tuzik erin begon te schrijven, maar er alleen in slaagde de eerste letter “t” in het bestand te schrijven, en dit bestand werd door iemand hernoemd, bijvoorbeeld naar olya.txt. Maar het bestand blijft hetzelfde, en ik wil er nog steeds mijn aas in opnemen. Elke keer dat een bestand wordt geopend door een systeemaanroep open in elke programmeertaal ontvang ik een unieke ID die mij naar een bestand verwijst, deze ID is de bestandsdescriptor. En het maakt helemaal niet uit wat en wie er vervolgens met dit bestand doet, het kan worden verwijderd, het kan worden hernoemd, de eigenaar kan worden gewijzigd of de lees- en schrijfrechten kunnen worden ontnomen, ik heb nog steeds toegang omdat ik op het moment dat ik het bestand opende de rechten had om het te lezen en/of te schrijven en ik erin slaagde ermee te gaan werken, wat betekent dat ik dat moet blijven doen.

In Linux opent de libc-bibliotheek 3 descriptorbestanden voor elke actieve applicatie (proces), genummerd 0,1,2. Meer informatie vindt u op de links man stdio и man stoer

  • Bestandsdescriptor 0 heet STDIN en is gekoppeld aan applicatie-invoer
  • Bestandsdescriptor 1 heet STDOUT en wordt door toepassingen gebruikt om gegevens uit te voeren, zoals afdrukopdrachten
  • Bestandsdescriptor 2 heet STDERR en wordt door toepassingen gebruikt om foutmeldingen uit te voeren.

Als u in uw programma een bestand opent om te lezen of te schrijven, krijgt u hoogstwaarschijnlijk de eerste gratis ID en dit is nummer 3.

De lijst met bestandsbeschrijvingen kan voor elk proces worden bekeken als u de PID kent.

Laten we bijvoorbeeld de bash-console openen en naar de PID van ons proces kijken

[user@localhost ]$ echo $$
15771

Laten we in de tweede console rennen

[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

Voor de doeleinden van dit artikel kunt u bestandsdescriptornummer 255 veilig negeren; het werd voor zijn behoeften geopend door bash zelf, en niet door de gekoppelde bibliotheek.

Nu zijn alle drie descriptorbestanden gekoppeld aan het pseudo-terminalapparaat /dev/pts, maar we kunnen ze nog steeds manipuleren, bijvoorbeeld door ze op een tweede console uit te voeren

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

En in de eerste console zullen we zien

[user@localhost ]$ hello world

Omleiden en pijpen

Je kunt deze 3 descriptorbestanden eenvoudig in elk proces overschrijven, inclusief in bash, bijvoorbeeld via een pijp die twee processen verbindt, zie

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

U kunt dit commando zelf uitvoeren met spoor -f en kijk wat er binnen gebeurt, maar ik zal het je kort vertellen.

Ons parent bash-proces met PID 15771 ontleedt onze opdracht en begrijpt precies hoeveel opdrachten we willen uitvoeren, in ons geval zijn het er twee: cat en sleep. Bash weet dat het twee onderliggende processen moet creëren en deze in één pijp moet samenvoegen. In totaal heeft bash twee onderliggende processen en één pijp nodig.

Bash voert een systeemaanroep uit voordat onderliggende processen worden gemaakt pijp en ontvangt nieuwe bestandsdescriptors op de tijdelijke pipe-buffer, maar deze buffer verbindt onze twee onderliggende processen nog niet.

Voor het ouderproces lijkt het erop dat er al een pipe is, maar dat er nog geen onderliggende processen zijn:

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

Gebruik dan de systeemoproep klonen bash maakt twee onderliggende processen, en onze drie processen zullen er als volgt uitzien:

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

Vergeet niet dat clone het proces samen met alle bestandsdescriptors kloont, zodat ze hetzelfde zullen zijn in het ouderproces en in de onderliggende processen. De taak van het ouderproces met PID 15771 is het bewaken van de onderliggende processen, dus wacht het eenvoudigweg op een reactie van de kinderen.

Daarom heeft het geen pipe nodig en sluit het de bestandsdescriptors genummerd 3 en 4.

In het eerste onderliggende bash-proces met PID 9004 wordt de systeemoproep gedaan dup2, verandert onze STDOUT-bestandsdescriptor nummer 1 in een bestandsdescriptor die naar pipe wijst, in ons geval is dit nummer 3. Alles wat het eerste kindproces met PID 9004 naar STDOUT schrijft, komt dus automatisch in de pipe-buffer terecht.

In het tweede onderliggende proces met PID 9005 gebruikt bash dup2 om de bestandsdescriptor STDIN-nummer 0 te wijzigen. Nu wordt alles wat onze tweede bash met PID 9005 zal lezen uit de pipe gelezen.

Hierna worden de bestandsdescriptors nummer 3 en 4 ook gesloten in de onderliggende processen, omdat ze niet langer worden gebruikt.

Ik negeer met opzet bestandsdescriptor 255; deze wordt door bash zelf voor interne doeleinden gebruikt en zal ook in onderliggende processen worden gesloten.

Vervolgens begint bash in het eerste kindproces met PID 9004 een systeemaanroep te gebruiken exec het uitvoerbare bestand dat we op de opdrachtregel hebben opgegeven, in ons geval is dit /usr/bin/cat.

In het tweede onderliggende proces met PID 9005 voert bash het tweede uitvoerbare bestand uit dat we hebben opgegeven, in ons geval /usr/bin/sleep.

De exec-systeemaanroep sluit de bestandsingangen niet, tenzij deze werden geopend met de vlag O_CLOEXEC op het moment dat de open aanroep werd gedaan. In ons geval worden na het starten van de uitvoerbare bestanden alle huidige bestandsdescriptors opgeslagen.

Controleer de 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

Zoals u ziet is het unieke nummer van onze leiding bij beide processen hetzelfde. We hebben dus een verband tussen twee verschillende processen met dezelfde ouder.

Voor degenen die niet bekend zijn met de systeemaanroepen die bash gebruikt, raad ik ten zeerste aan om de opdrachten via strace uit te voeren en te kijken wat er intern gebeurt, bijvoorbeeld als volgt:

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

Laten we terugkeren naar ons probleem met weinig schijfruimte en proberen gegevens op te slaan zonder het proces opnieuw te starten. Laten we een klein programma schrijven dat ongeveer 1 megabyte per seconde naar schijf schrijft. Bovendien, als we om de een of andere reden geen gegevens naar de schijf kunnen schrijven, zullen we dit eenvoudigweg negeren en proberen de gegevens binnen een seconde opnieuw te schrijven. In het voorbeeld dat ik Python gebruik, kun je elke andere programmeertaal gebruiken.

[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

Laten we het programma uitvoeren en naar de bestandsdescriptors kijken

[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

Zoals u kunt zien, hebben we onze drie standaardbestandsdescriptors en nog één die we hebben geopend. Laten we de bestandsgrootte controleren:

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

De gegevens worden geschreven, we proberen de machtigingen voor het bestand te wijzigen:

[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

We zien dat de gegevens nog steeds worden geschreven, hoewel onze gebruiker geen toestemming heeft om naar het bestand te schrijven. Laten we proberen het te verwijderen:

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

Waar worden de gegevens geschreven? En zijn ze überhaupt geschreven? Wij controleren:

[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, onze bestandsdescriptor bestaat nog steeds en we kunnen deze bestandsdescriptor behandelen als ons oude bestand, we kunnen het lezen, wissen en kopiëren.

Laten we eens kijken naar de bestandsgrootte:

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

De bestandsgrootte is 19923457. Laten we proberen het bestand te wissen:

[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

Zoals u kunt zien, neemt de bestandsgrootte alleen maar toe en werkte onze trunk niet. Laten we eens kijken naar de systeemaanroepdocumentatie open. Als we de vlag O_APPEND gebruiken bij het openen van een bestand, controleert het besturingssysteem bij elke schrijfbeurt de bestandsgrootte en schrijft gegevens helemaal naar het einde van het bestand, en doet dit atomair. Hierdoor kunnen meerdere threads of processen naar hetzelfde bestand schrijven. Maar in onze code gebruiken we deze vlag niet. We kunnen alleen een andere bestandsgrootte in lsof na trunk zien als we het bestand openen voor verder schrijven, wat in plaats daarvan in onze code betekent

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

wij moeten zetten

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

Controleren met de “w”-vlag

[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

en met de "a"-vlag

[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

Programmeren van een reeds lopend proces

Vaak gebruiken programmeurs bij het maken en testen van programma's debuggers (bijvoorbeeld GDB) of verschillende logniveaus in de applicatie. Linux biedt de mogelijkheid om een ​​reeds actief programma daadwerkelijk te schrijven en te wijzigen, bijvoorbeeld de waarden van variabelen te wijzigen, een breekpunt in te stellen, enz., enz.

Terugkerend naar de oorspronkelijke vraag over onvoldoende schijfruimte om een ​​bestand te schrijven, laten we proberen het probleem te simuleren.

Laten we een bestand maken voor onze partitie, dat we als een afzonderlijke schijf zullen mounten:

[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 ~]$

Laten we een bestandssysteem maken:

[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 ~]$

Mount het bestandssysteem:

[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

We maken een directory aan met onze eigenaar:

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

Laten we het bestand openen om alleen in ons programma te schrijven:

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

Lancering

[user@localhost ]$ python openforwrite.py 

Wij wachten een paar seconden

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

We hebben dus het probleem dat aan het begin van dit artikel wordt beschreven. Vrije ruimte 0, 100% bezet.

We herinneren ons dat we, afhankelijk van de omstandigheden van de taak, zeer belangrijke gegevens proberen vast te leggen die niet verloren kunnen gaan. En tegelijkertijd moeten we de service repareren zonder het proces opnieuw te starten.

Laten we zeggen dat we nog steeds schijfruimte hebben, maar op een andere partitie, bijvoorbeeld in /home.

Laten we proberen onze code “on the fly” te herprogrammeren.

Laten we eens kijken naar de PID van ons proces, die alle schijfruimte in beslag heeft genomen:

[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

Maak verbinding met het proces via gdb

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

Laten we eens kijken naar de open bestandsdescriptors:

(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

We kijken naar de informatie over bestandsbeschrijving nummer 3, die ons interesseert

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

Als we in gedachten houden welke systeemaanroep Python doet (zie hierboven waar we strace uitvoerden en de open oproep vonden), doen we bij het verwerken van onze code om een ​​bestand te openen hetzelfde namens ons proces, maar we hebben de O_WRONLY|O_CREAT| O_TRUNC bits vervangen door een numerieke waarde. Open hiervoor bijvoorbeeld de kernelbronnen hier en kijk welke vlaggen waarvoor verantwoordelijk zijn

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

We combineren alle waarden in één, we krijgen 00001101

We voeren onze oproep uit vanuit gdb

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

Dus we hebben een nieuwe bestandsdescriptor met nummer 4 en een nieuw geopend bestand op een andere partitie, we controleren:

(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

We herinneren ons het voorbeeld met pipe - hoe bash bestandsdescriptors verandert, en we hebben de dup2-systeemaanroep al geleerd.

We proberen de ene bestandsdescriptor te vervangen door een andere

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

controleren:

(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

We sluiten bestandsdescriptor 4, omdat we deze niet nodig hebben:

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

En verlaat 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

Het nieuwe bestand controleren:

[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

Zoals u kunt zien, worden de gegevens naar een nieuw bestand geschreven. Laten we het oude bestand controleren:

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

Er gaan geen gegevens verloren, de applicatie werkt, logs worden naar een nieuwe locatie geschreven.

Laten we de taak een beetje ingewikkelder maken

Laten we ons voorstellen dat de gegevens belangrijk voor ons zijn, maar dat we op geen enkele partitie schijfruimte hebben en dat we de schijf niet kunnen verbinden.

Wat we kunnen doen is onze gegevens ergens omleiden, bijvoorbeeld naar pipe, en op zijn beurt de gegevens van pipe naar het netwerk omleiden via een programma, bijvoorbeeld netcat.
We kunnen een benoemde pipe maken met de opdracht mkfifo. Er wordt een pseudobestand op het bestandssysteem gemaakt, zelfs als er geen vrije ruimte op is.

Start de applicatie opnieuw en controleer:

[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

Er is geen schijfruimte, maar we hebben daar met succes een benoemde pipe gemaakt:

[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

Nu moeten we op de een of andere manier alle gegevens die in deze pijp terechtkomen via het netwerk naar een andere server wikkelen; dezelfde netcat is hiervoor geschikt.

Op de server remote-server.example.com starten we

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

Op onze problematische server starten we in een aparte terminal

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

Nu gaan alle gegevens die in de pipe terechtkomen automatisch naar stdin in netcat, die deze naar het netwerk op poort 7777 stuurt.

Het enige wat we moeten doen is beginnen met het schrijven van onze gegevens in deze benoemde pijp.

We hebben de applicatie al actief:

[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

Van alle vlaggen hebben we alleen O_WRONLY nodig omdat het bestand al bestaat en we het niet hoeven te wissen

[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

De externe server remote-server.example.com controleren

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

De gegevens komen eraan, we controleren de probleemserver

[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

De gegevens worden opgeslagen, het probleem is opgelost.

Ik maak van deze gelegenheid gebruik om hallo te zeggen tegen mijn collega's van Degiro.
Luister naar podcasts van Radio-T.

Goed voor iedereen.

Als huiswerk stel ik voor dat u nadenkt over wat er in de procesbestandsdescriptors cat en sleep zal staan ​​als u de volgende opdracht uitvoert:

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

Bron: www.habr.com

Voeg een reactie