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 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 en
- 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. , 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 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 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 , 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 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. Als 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 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
