Bestandshandleiding in Linux met voorbeelden

Eens, tijdens een sollicitatiegesprek, werd me gevraagd wat ik zou doen als ik ontdekte dat een service niet werkte omdat de schijf vol was.

Natuurlijk antwoordde ik dat ik zou kijken wat die ruimte in beslag nam en, indien mogelijk, ruimte zou vrijmaken.
Toen vroeg de interviewer wat ik zou doen als er geen vrije ruimte op de partitie is, maar er zijn ook geen bestanden die alle ruimte in beslag nemen.

Hierop antwoordde ik dat je altijd de open bestandsdescriptoren kunt bekijken, bijvoorbeeld met de lsof-opdracht, om te begrijpen welke applicatie al het beschikbare volume in beslag neemt, en daarna afhankelijk van de situatie actie kunt ondernemen, afhankelijk van of de gegevens nodig zijn.

De interviewer onderbrak me op het laatste woord, en vulde zijn vraag aan: "Stel dat we de gegevens niet nodig hebben, het is gewoon een debug-log, maar de applicatie werkt niet omdat deze geen debug kan schrijven?"

"Oké," antwoordde ik, "we kunnen de debug in de applicatieconfiguratie uitzetten en deze opnieuw opstarten."
De interviewer weerlegde: "Nee, we kunnen de applicatie niet opnieuw opstarten, we hebben nog steeds belangrijke gegevens in het geheugen staan, en belangrijke klanten zijn verbonden met de service, die we niet kunnen dwingen opnieuw te verbinden."

"Nou goed," zei ik, "als we de applicatie niet kunnen herstarten en de gegevens zijn niet belangrijk, dan kunnen we gewoon dit open bestand via de bestandsdescriptor wissen, zelfs als we het niet zien met de ls-opdracht op het bestandssysteem."

De interviewer was tevreden, maar ik niet.

Toen dacht ik, waarom graaft de persoon die mijn kennis controleert niet dieper? Wat als de gegevens toch belangrijk zijn? Wat als we het proces niet opnieuw kunnen opstarten, en dit proces schrijft naar het bestandssysteem op een partitie zonder vrije ruimte? Wat als we niet alleen de reeds geschreven gegevens kunnen verliezen, maar ook de gegevens die dit proces schrijft of probeert te schrijven?

Tuzik

Aan het begin van mijn carrière probeerde ik een kleine applicatie te maken waarin informatie over gebruikers moest worden opgeslagen. Toen vroeg ik me af hoe ik gebruikers aan hun gegevens kon koppelen. Er is bijvoorbeeld Ivan Ivanovich Ivanov, en hij heeft bepaalde gegevens, maar hoe kan ik deze met elkaar verbinden? Ik kan direct aangeven dat de hond genaamd "Tuzik" aan hem toebehoort. Maar wat als hij zijn naam verandert en in plaats van Ivan bijvoorbeeld Olya wordt? Dan zal onze Olya Ivanovna Ivanova geen hond meer hebben, terwijl onze Tuzik nog steeds toebehoort aan de niet-bestaande Ivan. Dit probleem werd opgelost door een database, die elke gebruiker een unieke identifier (ID) gaf, en mijn Tuzik werd hiermee aan die ID gekoppeld, die in wezen gewoon een volgnummer was. Op deze manier had de eigenaar van Tuzik de ID met nummer 2, en op een gegeven moment was dat Ivan, en later werd het Olya. Het probleem van de mensheid en de veeteelt was praktisch opgelost.

Bestandsdescriptor

Het probleem met het bestand en het programma dat met dat bestand werkt, is ongeveer hetzelfde als dat van onze hond en de eigenaar. Stel dat ik een bestand met de naam ivan.txt heb geopend en ik begon het woord tuzik te schrijven, maar ik had alleen de eerste letter "t" in het bestand opgenomen, en dit bestand werd door iemand hernoemd, bijvoorbeeld in olya.txt. Maar het bestand bleef hetzelfde, en ik wil nog steeds schrijven over mijn Tuzik. Elke keer dat ik het bestand open via een systeembel open in elke programmeertaal ontvang ik een unieke ID die naar het bestand verwijst, deze ID is de bestandsdescriptor. Het maakt helemaal niet uit wat en wie er verder met dit bestand doet; het kan worden verwijderd, het kan worden hernoemd, het kan van eigenaar veranderen of de lees- en schrijfrechten kunnen worden ingetrokken, ik heb nog steeds toegang, omdat ik op het moment van het openen van het bestand de rechten had om het te lezen en/of te schrijven en ik was al begonnen ermee te werken, dus ik moet daarmee doorgaan.

In Linux opent de libc-bibliotheek 3 bestandsdescriptors voor elke actieve applicatie (proces) met de nummers 0, 1, 2. Meer informatie vindt u op de volgende links man stdio en man stdout

  • Bestandsdescriptor 0 wordt STDIN genoemd en is geassocieerd met de gegevensinvoer van de applicatie
  • Bestand descriptor 1 wordt STDOUT genoemd en wordt door applicaties gebruikt voor gegevensuitvoer, zoals print-commando's.
  • Bestand descriptor 2 wordt STDERR genoemd en wordt door applicaties gebruikt voor gegevensuitvoer die fouten rapporteren.

Als je in je programma een bestand opent voor lezen of schrijven, krijg je waarschijnlijk de eerste beschikbare ID en dat zal nummer 3 zijn.

Je kunt de lijst van bestand descriptors bekijken voor elk proces als je de PID kent.

Bijvoorbeeld, laten we de console met bash openen en de PID van ons proces bekijken.

[user@localhost ]$ echo $$
15771

Op de tweede console starten we

[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

Bestand descriptor met nummer 255 kan je gerust negeren in het kader van dit artikel; hij is geopend voor zijn eigen behoeften door bash zelf en niet door de gelinkte bibliotheek.

Momenteel zijn alle 3 bestand descriptors verbonden met een pseudo-terminal. /dev/pts, maar we kunnen er nog steeds mee omgaan; laten we bijvoorbeeld in de tweede console

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

En in de eerste console zullen we zien

[user@localhost ]$ hello world

Redirectie en Pipe

Je kunt deze 3 bestand descriptors eenvoudig opnieuw definiëren in elk proces, inclusief bash, bijvoorbeeld via een pijp (pipe) die twee processen verbindt; laten we kijken.

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

Je kunt dit commando zelf uitvoeren met strace -f en zien wat er binnenin gebeurt, maar ik zal het kort uitleggen.

Ons ouderproces bash met PID 15771 parseert ons commando en begrijpt hoeveel commando's we precies willen uitvoeren; in ons geval zijn het er twee: cat en sleep. Bash weet dat het twee voortgezette processen moet creëren en ze met een pijp moet verbinden. In totaal heeft bash 2 voortgezette processen en een pijp nodig.

Voordat bash voortgezette processen aanmaakt, voert het een systeemaanroep uit pipe en krijgt nieuwe bestand descriptors voor de tijdelijke buffer van de pijp, maar deze buffer koppelt op dat moment onze twee voortgezette processen nog niet.

Voor het ouderproces lijkt het alsof de pijp er al is, maar de voortgezette processen nog niet:

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

Vervolgens maakt de systeembel met behulp van een systeemaanroep clone bash twee dochterprocessen aan, en onze drie processen zullen er als volgt uitzien:

PID    commando
15771  bash
lrwx------ 1 gebruiker gebruiker 64 Okt  7 15:42 0 -> /dev/pts/21
lrwx------ 1 gebruiker gebruiker 64 Okt  7 15:42 1 -> /dev/pts/21
lrwx------ 1 gebruiker gebruiker 64 Okt  7 15:42 2 -> /dev/pts/21
lrwx------ 1 gebruiker gebruiker 64 Okt  7 15:42 3 -> pipe:[253543032]
lrwx------ 1 gebruiker gebruiker 64 Okt  7 15:42 4 -> pipe:[253543032]
lrwx------ 1 gebruiker gebruiker 64 Okt  7 15:42 255 -> /dev/pts/21
PID    commando
9004  bash
lrwx------ 1 gebruiker gebruiker 64 Okt  7 15:57 0 -> /dev/pts/21
lrwx------ 1 gebruiker gebruiker 64 Okt  7 15:57 1 -> /dev/pts/21
lrwx------ 1 gebruiker gebruiker 64 Okt  7 15:57 2 -> /dev/pts/21
lrwx------ 1 gebruiker gebruiker 64 Okt  7 15:57 3 -> pipe:[253543032]
lrwx------ 1 gebruiker gebruiker 64 Okt  7 15:57 4 -> pipe:[253543032]
lrwx------ 1 gebruiker gebruiker 64 Okt  7 15:57 255 -> /dev/pts/21
PID    commando
9005  bash
lrwx------ 1 gebruiker gebruiker 64 Okt  7 15:57 0 -> /dev/pts/21
lrwx------ 1 gebruiker gebruiker 64 Okt  7 15:57 1 -> /dev/pts/21
lrwx------ 1 gebruiker gebruiker 64 Okt  7 15:57 2 -> /dev/pts/21
lrwx------ 1 gebruiker gebruiker 64 Okt  7 15:57 3 -> pipe:[253543032]
lrwx------ 1 gebruiker gebruiker 64 Okt  7 15:57 4 -> pipe:[253543032]
lrwx------ 1 gebruiker gebruiker 64 Okt  7 15:57 255 -> /dev/pts/21

Vergeet niet dat clone het proces samen met alle bestands-deskryptoren kloont, dus in het ouderproces en de dochterprocessen zullen ze hetzelfde zijn. De taak van het ouderproces met PID 15771 is om de dochterprocessen in de gaten te houden, dus wacht hij gewoon op een antwoord van de dochterprocessen.

Daarom heeft hij de pipe niet nodig, en sluit hij de bestands-deskryptoren met nummers 3 en 4.

In het eerste dochterproces bash met PID 9004, verandert de systeemaanroep dup2, onze STDOUT-bestandsdeskriptor met nummer 1 naar de bestands-deskriptor die naar de pipe verwijst, in ons geval is dit nummer 3. Op deze manier zal alles wat het eerste dochterproces met PID 9004 naar STDOUT schrijft automatisch in de buffer van de pipe komen.

In het tweede dochterproces met PID 9005, verandert bash met behulp van dup2 de bestands-deskriptor voor STDIN met nummer 0. Nu zal alles wat onze tweede bash met PID 9005 leest uit de pipe komen.

Daarna worden in de dochterprocessen ook de bestands-deskryptoren met nummers 3 en 4 gesloten, aangezien ze niet meer worden gebruikt.

De bestands-deskryptor 255 negeer ik opzettelijk, deze wordt gebruikt voor interne behoeften van bash zelf en zal ook in de dochterprocessen worden gesloten.

Vervolgens start in het eerste dochterproces met PID 9004 bash met behulp van een systeemaanroep exec het uitvoerbare bestand dat we op de opdrachtregel hebben opgegeven, in ons geval is dit /usr/bin/cat.

In het tweede dochterproces met PID 9005 start bash het tweede uitvoerbare bestand dat we hebben opgegeven, in ons geval is dit /usr/bin/sleep.

De systeemaanroep exec sluit geen bestanddescriptoren wanneer deze niet zijn geopend met de O_CLOEXEC-vlag tijdens de aanroep open. In ons geval blijven alle huidige bestanddescriptoren behouden na het starten van uitvoerbare bestanden.

Laten we het in de console controleren:

[user@localhost ]$ pgrep -P 15771
9004
9005
[user@localhost ]$ ls -lah /proc/15771/fd/
totaal 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
totaal 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
totaal 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
GEBRUIKER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIJD OPDRACHT
user  9004  0.0  0.0 107972   620 pts/21   S+   15:57   0:00 cat /dev/zero
[user@localhost ]$ ps -up 9005
GEBRUIKER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIJD OPDRACHT
user  9005  0.0  0.0 107952   360 pts/21   S+   15:57   0:00 sleep 10000

Zoals u kunt zien, komt het unieke nummer van onze pipe in beide processen overeen. Op deze manier hebben we een verbinding 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 commando's via strace uit te voeren en te kijken wat er binnenin gebeurt, bijvoorbeeld zo:

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

Laten we teruggaan naar ons probleem van onvoldoende schijfruimte en de poging om gegevens op te slaan zonder het proces opnieuw te starten. We schrijven een klein programma dat ongeveer 1 megabyte per seconde naar de schijf schrijft. Als we om welke reden dan ook geen gegevens op de schijf kunnen schrijven, negeren we dit en proberen we het daarna opnieuw na een seconde. In het voorbeeld gebruik ik Python, maar u kunt 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 starten en kijken naar de bestanddescriptoren

[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 we zien, hebben we onze 3 standaard bestandshandleidingen 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

Gegevens worden geschreven, laten we proberen de rechten van 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 gegevens nog steeds worden geschreven, hoewel onze gebruiker geen rechten heeft om in 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 worden ze überhaupt geschreven? Laten we 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 bestandshandleiding bestaat nog steeds, en we kunnen met deze bestandshandleiding werken zoals met ons oude bestand; we kunnen het lezen, wissen en kopiëren.

Laten we naar de bestandsgrootte kijken:

[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 we zien, neemt de bestandsgrootte alleen maar toe en werkt onze truncate niet. Laten we de documentatie over het systeemoproepraadplegen. openAls we bij het openen van het bestand de vlag O_APPEND gebruiken, controleert het besturingssysteem bij elke schrijfopdracht de bestandsgrootte en schrijft het de gegevens aan het einde van het bestand, en dit gebeurt atomair. Dit stelt meerdere threads of processen in staat om naar hetzelfde bestand te schrijven. Maar in onze code gebruiken we deze vlag niet. We kunnen een andere bestandsgrootte zien in lsof na truncate alleen als we het bestand voor append openen, wat betekent dat we in onze code in plaats van

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

we moeten schrijven

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

Laten we 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

Een al draaiend proces programmeren

Programma's worden vaak door programmeurs tijdens de ontwikkeling en het testen gedebugd (bijvoorbeeld met GDB) of er worden verschillende niveaus van logging in de applicatie gebruikt. Linux biedt de mogelijkheid om feitelijk een al draaiend programma te schrijven en te wijzigen, bijvoorbeeld door variabele waarden te veranderen, breakpoints in te stellen, enzovoorts.

Terugkomend op de oorspronkelijke vraag over gebrek aan schijfruimte voor het opslaan van een bestand, laten we het probleem simuleren.

Laten we een bestand aanmaken voor onze partitie, die we als een aparte schijf gaan aankoppelen:

[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) gekopieerd, 0.00525929 s, 2.0 GB/s
[user@localhost ~]$

Laten we een bestandssysteem aanmaken:

[user@localhost ~]$ mkfs.ext4 ~/tempfile_for_article.dd
mke2fs 1.42.9 (28-dec-2013)
/home/user/tempfile_for_article.dd is geen blokspeciaal apparaat.
Toch doorgaan? (y,n) y
...
Superblocks en bestandssysteem accountinginformatie schrijven: klaar
[user@localhost ~]$

Laten we het bestandssysteem aankoppelen:

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

Laten we een map aanmaken met onze eigenaar:

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

Laten we het bestand alleen voor schrijven openen in ons programma:

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

We starten

[user@localhost ]$ python openforwrite.py 

Even wachten...

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

Dus, we hebben het probleem gekregen dat in het begin van dit artikel werd beschreven. Vrije ruimte 0, gebruikte 100%.

We herinneren ons dat we in de situatie proberen zeer belangrijke gegevens te schrijven die niet verloren mogen gaan. En we moeten de service repareren zonder het proces opnieuw op te starten.

Stel dat we toch schijfruimte hebben, maar in een andere partitie, bijvoorbeeld in /home.

Laten we proberen om onze code 'on-the-fly' opnieuw te programmeren.

We kijken naar de PID van ons proces dat al het schijfruimte heeft opgeslokt:

[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

We verbinden met het proces via gdb

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

We bekijken de geopende bestandsdescriptors:

(gdb) shell ls -lah /proc/10078/fd/
totaal 0
dr-x------ 2 user user  0 8 okt 11:06 .
dr-xr-xr-x 9 user user  0 8 okt 11:06 ..
lrwx------ 1 user user 64 8 okt 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 8 okt 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 8 okt 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 8 okt 11:09 3 -> /mnt/logs/123.txt

Laten we de informatie bekijken over de bestandsdescriptor met nummer 3, die ons interesseert.

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

Met in gedachten dat we weten welke systeemaanroep Python maakt (zie hierboven waar we strace hebben uitgevoerd en de aanroep open hebben gevonden), zodat we onze code om een bestand te openen handmatig vanuit ons proces kunnen verwerken, maar de bits O_WRONLY|O_CREAT|O_TRUNC moeten we vervangen door een numerieke waarde. Daarom openen we de kernelbronnen, bijvoorbeeld here en we kijken welke vlaggen waarvoor verantwoordelijk zijn

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

We combineren alle waarden in één en krijgen 00001101

We starten onze aanroep 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 schijf, 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 de bestandsdescriptoren verandert, en we hebben al de systeemaanroep dup2 geleerd.

Laten we proberen één bestandsdescriptor door een andere te vervangen

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

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 -> /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 die niet nodig hebben:

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

En we verlaten 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

We controleren het nieuwe bestand:

[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 we zien, worden de gegevens naar het nieuwe bestand geschreven, we controleren het oude:

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

Gegevens zijn niet verloren gegaan, de applicatie werkt, logs worden op nieuwe locatie geschreven.

We maken de taak iets moeilijker

Stel je voor dat onze gegevens belangrijk zijn, maar we hebben geen ruimte op de schijf in een van de partities en we kunnen geen schijf aansluiten.

Wat we kunnen doen, is onze gegevens ergens naartoe omleiden, bijvoorbeeld naar een pipe, en de gegevens uit de pipe vervolgens via een programma, bijvoorbeeld netcat, naar het netwerk omleiden.
We kunnen een named pipe maken met het commando mkfifo. Dit zal een pseudo-bestand op het bestandssysteem creëren, zelfs als er geen vrije ruimte op is.

Herstarten we de applicatie en controleren we:

[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 plaats op de schijf, maar we maken daar met succes een named pipe aan:

[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 alle gegevens die binnenkomen in deze pipe op de andere server via het netwerk verpakken, hiervoor kunnen we gewoon netcat gebruiken.

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

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

Op onze probleemserver starten we in een aparte terminal

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

Nu komen alle gegevens die in de pipe binnenkomen automatisch op stdin in netcat, dat ze via het netwerk naar poort 7777 verzendt.

Wat we nu nog moeten doen, is onze gegevens in deze named pipe schrijven.

We hebben al een draaiende applicatie:

[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

We controleren de externe server remote-server.example.com

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

Data is onderweg, 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 data is opgeslagen, het probleem is opgelost.

Bij deze stuur ik groeten aan de collega’s van Degiro.
Luister naar de podcasts van Radio-T.

Ik wens iedereen het beste.

Als huiswerk stel ik voor na te denken over wat er in de bestanddescriptoren van het proces cat en sleep zal staan als je zo'n commando uitvoert:

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

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster