Linuxi failide deskriptor koos näidistega

Ühel intervjuhil küsiti minult, mida teeksin, kui avastaksin, et teenus ei tööta, kuna kettal on ruum otsas?

Muidugi vastasin, et vaataksin, milles see ruum on kinni ja kui võimalik, siis puhastaksin ruumi.
Siis küsis intervjueerija, aga mis siis, kui jaotises ei ole vaba ruumi, kuid ka faile, mis kogu ruumi peaksid kasutama, sa ei näe?

Sellele vastasin, et alati saab vaadata avatud failide kirjeldajaid, näiteks käsuga lsof, ja mõista, milline rakendus on kogu saadaval oleva ruumi ära kasutanud, ning seejärel toimetada vastavalt olukorrale, sõltuvalt sellest, kas andmed on vajalikud.

Intervjueerija katkestas mind viimases sõnas, lisades oma küsimusele: "Kujutame ette, et andmed ei ole meile vajalikud, see on lihtsalt tõrke logi, kuid rakendus ei tööta, kuna ei saa kirjutada tõrke logi"?

"Okei," vastasin, "me võime rakenduse konfis tõrke logimise välja lülitada ja selle uuesti käivitada."
Intervjueerija vaidlustas: "Ei, rakendust me ei saa uuesti käivitada, meil on mälus endiselt olulised andmed ja sellele teenusele on ühendatud olulised kliendid, keda me ei saa sundida uuesti ühendust looma."

"Noh, hea," ütlesin, "kui me ei saa rakendust käivitada ja andmed ei ole meile olulised, siis saame lihtsalt selle avatud faili läbi failide kirjeldaja puhastada, isegi kui me ei näe seda käsus ls failisüsteemis."

Intervjueerija jäi rahule, aga mina mitte.

Siis mõtlesin, miks inimene, kes kontrollib minu teadmisi, ei kaeva sügavamale? Mis siis, kui andmed on ikkagi olulised? Mis siis, kui me ei saa protsessi taaskäivitada, ja samal ajal kirjutab see protsess failisüsteemi jaotisse, kus ei ole vaba ruumi? Mis siis, kui me ei saa kaotada mitte ainult juba kirjutatud andmeid, vaid ka neid andmeid, mida see protsess kirjutab või püüab kirjutada?

Tuzik

Oma karjääri alguses püüdsin luua väikest rakendust, kus oli vaja hoida teavet kasutajate kohta. Siis mõtlesin, kuidas siduda kasutajat tema andmetega. Näiteks on mul Ivan Ivanov, kellel on mingid andmed, kuid kuidas neid omavahel siduda? Võin otse näidata, et koer nimega „Tuzik” kuulub just sellele Ivanile. Kuid mis siis, kui ta vahetab nime ja temast saab näiteks Olya? Siis selgub, et meie Olya Ivanovna Ivanova ei omagi enam koera, kuid meie Tuzik kuulub endiselt olematule Ivanile. Probleemi aitas lahendada andmebaas, mis andis igale kasutajale unikaalse identifikaatori (ID), ja mu Tuzik oli seotud selle ID-ga, mis oli sisuliselt lihtsalt järjestusnumber. Nii oli Tuziku omanikul ID number 2, ja mingil ajahetkel oli selle all Ivan, hiljem aga Olya. Inimkonna ja loomade probleemi saime praktiliselt lahendada.

Faili deskriptor

Faili ja selle faili avava programmi probleem on umbes sama, mis meie koera ja inimese probleem. Oletame, et avasin faili nimega ivan.txt ja hakkasin sinna kirjutama sõna tuzik, kuid suutsin kirjutada vaid esimese tähe „t” ja see fail nimetati kellegi poolt ümber, näiteks olya.txt. Kuid fail jäi endiseks ja ma tahan endiselt kirjutada oma Tuzik-ist. Iga kord, kui avatud failiga süsteemi kutsumi, open kõigis programmeerimiskeeltes saan unikaalse ID, mis näitab mulle faili, see ID ongi faili deskriptor. Ja täiesti pole oluline, mida ja kes selle failiga hiljem teeb, seda võidakse kustutada, seda võidakse ümber nimetada, selle omanikku võidakse vahetada või lugemis- ja kirjutamisõigusi võidakse võtta, mul on ikkagi sellele juurdepääs, sest faili avamise hetkel olid mul vahemälu selle lugemiseks ja/või kirjutamiseks ning ma suutsin alustada selle kasutamist, seega pean seda edasi tegema.

Linuxis avab libc igale käimasolevale rakendusele (protsessile) 3 faili deskriptorit numbritega 0, 1, 2. Lisainfot leiate linkidelt. man stdio ja man stdout

  • Faili deskriptor 0 nimetatakse STDIN-iks ja on seotud rakenduse andmete sisestamisega.
  • Faili deskriptor 1 nimega STDOUT, mida rakendused kasutavad andmete väljundiks, näiteks print käsud.
  • Faili deskriptor 2 nimega STDERR, mida rakendused kasutavad andmete, mis teatavad vigadest, väljundiks.

Kui avate oma programmis mõne faili lugemiseks või kirjutamiseks, siis tõenäoliselt saate esimese vaba ID ja see on number 3.

Failide deskriptorite loendit saab vaadata igas protsessis, kui te küsite selle PID-d.

Näiteks avame bash konsooli ja vaatame oma protsessi PID-d.

[user@localhost ]$ echo $$
15771

Teises konsoolis käivitame

[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

Faili deskriptorit nummer 255 võite selles artiklis julgelt ignoreerida, see oli avatud bash enda vajaduste jaoks, mitte linkitud teegi jaoks.

Praegu on kõik 3 faili deskriptorit seotud pseudo-terminali seadmega. /dev/pts, kuid me saame siiski nendega manipuleerida, näiteks käivitame teises konsoolis

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

Ja esimeses konsoolis näeme me

[user@localhost ]$ hello world

Redirect ja Pipe

Saate need 3 faili deskriptorit igas protsessis, sealhulgas bash, hõlpsasti ümber määrata, näiteks läbi toru (pipe), mis ühendab kahte protsessi, vaatame

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

Võite ise selle käsu käivitada ja näha, mis juhtub, kuid räägin teile lühidalt. strace -f Meie vanem protsess bash, mille PID on 15771, analüüsib meie käsku ja mõistab, kui palju käske me soovime käivitada, meie juhul on neid kaks: cat ja sleep. Bash teab, et tal on vaja luua kaks tütart protsessi ja need ühte torusse ühendada. Kokku vajab bash 2 tütart protsessi ja ühte pipe'i.

Enne tütarprotsesside loomist käivitab bash süsteemi kutse

ja saab uusi faili deskriptoreid ajutisele toru puhvri jaoks, kuid see puhver ei seo praegu meie kaht tütarprotsessi. pipe Vanema protsessi jaoks näeb see välja nagu pipe oleks juba olemas, kuid tütarprotsessid pole veel loodud:

PID käsk 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
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

Seejärel kasutab süsteemikõnet clone bash loob kaks tütart protsessi ja meie kolm protsessi näevad välja nii:

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

Ärge unustage, et clone kloonib protsessi koos kõigi failikuvanditega, seega on need vanem- ja tütart protsessides ühesugused. Vanemprotsessi, mille PID on 15771, ülesanne on jälgida tütart protsesse, seega ootab see lihtsalt vastust tütartelt.

Seega ei ole tal pipe'i vaja ja ta sulgeb failikuvandid numbritega 3 ja 4.

Esimeses tütart protsessis bashiga, mille PID on 9004, süsteemikõne dup2, muudab meie STDOUT failikuvandi numbriga 1 failikuvandiks, mis näitab pipe'i, meie juhul on see number 3. Nii et kõik, mis esimene tütart protsess PID 9004 kirjutab STDOUT-i, jõuab automaatselt pipe'i puhvri.

Teises tütart protsessis PID 9005 bash muudab failikuvandi STDIN numbriga 0, kasutades dup2. Nüüd loeb kõik, mida meie teine bash PID 9005 loeb, pipe'ist.

Pärast seda suletakse tütart protsessides samuti failikuvandid numbritega 3 ja 4, kuna neid ei kasutata enam.

Failikuvand 255 ignoreerin ma tahtlikult, seda kasutatakse bash-i enda sisemiste vajaduste jaoks ja tütart protsessides sulgetakse see samuti.

Seejärel käivitab esimeses tütart protsessis PID 9004 bash süsteemikõne abil exec täidetava faili, mille oleme määranud käsureas, meie puhul on see /usr/bin/cat.

Teises tütart protsessis PID 9005 käivitab bash teise täidetava faili, mille oleme määranud, meie puhul on see /usr/bin/sleep.

Süsteemikõne exec ei sulge faili deskriptereid, kui need ei olnud avatud O_CLOEXEC lipuga open'i kutsumise ajal. Meie puhul jäävad pärast täidetavate failide käivitamist kõik praegused faili deskripterid alles.

Kontrollime konsoolis:

[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

Nagu näete, on meie toru ainulaadne number mõlemas protsessis sama. Seega on meil seos kahe erineva protsessi vahel, millel on üks vanem.

Neile, kes ei ole tuttavad bash'i kasutatavate süsteemikõnedega, soovitan tungivalt käivitada käsud strace'i kaudu ja vaadata, mis toimub seespool, näiteks nii:

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

Naaseme meie probleemiga ketta ruumi puudumise ja andmete salvestamise katse juurde ilma protsessi taaskäivitamata. Kirjutame väikese programmi, mis kirjutab diskile umbes 1 megabait sekundis. Samas, kui me mingil põhjusel ei suuda andmeid diskile kirjutada, ignoreerime seda lihtsalt ja proovime jälle järgmise sekundi jooksul andmeid kirjutada. Näites kasutan ma Pythonit, aga võite kasutada mõnda muud programmeerimiskeelt.

[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

Käivitame programmi ja vaatame faili deskriptereid

[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

Nagu näeme, on meil olemas meie 3 standardset failikirjeldajat ja veel üks, mille avasime. Kontrollime faili suurust:

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

andmed kirjutatakse, proovime faili õigusi muuta:

[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

Nägime, et andmeid jätkuvalt kirjutatakse, kuigi meie kasutajal ei ole õigust faili kirjutada. Proovime selle kustutada:

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

Kuhu andmed kirjutatakse? Kas nad kirjutatakse üldse? Kontrollime:

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

Jah, meie failikirjeldaja on endiselt olemas ja me saame seda failikirjeldajat kasutada nagu meie vana faili, me saame seda lugeda, tühjendada ja kopeerida.

Vaadake faili suurust:

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

Faili suurus on 19923457. Proovime faili tühjendada:

[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

Nagu näeme, suureneb faili suurus ainult ja meie tühjendamine ei töötanud. Viitame süsteemikõne dokumentatsioonile open. Kui me avame faili O_APPEND lipuga, siis igal kirjutamisel kontrollib operatsioonisüsteem faili suurust ja kirjutab andmed faili lõppu, tehes seda aatomiliselt. See võimaldab mitmetel lõimedel või protsessidel kirjutada ühte ja sama faili. Kuid meie koodis me seda lippu ei kasuta. Saame 'lsof' väljundis pärast tühjendamist näha teist faili suurust ainult juhul, kui avame faili lisamiseks, seega peaksime meie koodis

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

asendama

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

Kontrollime 'w' lipuga

[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

ja "a" lipukesega

[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

Programmeerime juba käimasolevat protsessi

Sageli kasutavad arendajad programmide loomisel ja testimisel debugeerijaid (nt GDB) või rakenduses mitmeid logimise tasemeid. Linux annab võimaluse tegelikult kirjutada ja muuta juba käimasolevat programmi, näiteks muuta muutujate väärtusi, seada peatuspunkte jne.

Naaseme algse küsimuse juurde, kus oli probleem diskiruumi puudumise tõttu faili kirjutamiseks, ja proovime emuleerida probleemi.

Loome faili meie partitsiooni jaoks, mille me monteerime kui eraldi ketas:

[user@localhost ~]$ dd if=/dev/zero of=~/tempfile_for_article.dd bs=1M count=10
10+0 salvestust sisse
10+0 salvestust välja
10485760 baiti (10 MB) kopeeritud, 0.00525929 s, 2.0 GB/s
[user@localhost ~]$

Loome failisüsteemi:

[user@localhost ~]$ mkfs.ext4 ~/tempfile_for_article.dd
mke2fs 1.42.9 (28-Dec-2013)
/home/user/tempfile_for_article.dd ei ole plokk-spetsiaalne seade.
Jätkata ikka? (y,n) y
...
Kirjutamine superblokidesse ja failisüsteemi arvestusteabe: valmis
[user@localhost ~]$

Monteerime failisüsteemi:

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

Loome kaust meie omanikuga:

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

Avame faili ainult kirjutamiseks meie programmis:

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

Käivitame

[user@localhost ]$ python openforwrite.py 

Ootame paar sekundit

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

Nii oleme saanud probleemi, millest käesoleva artikli alguses rääkisime. Vaba ruumi 0, kasutatud 100%.

Me mäletame, et ülesande tingimustest tulenevalt üritame salvestada väga olulisi andmeid, mida ei saa kaotada. Ja samas peame teenuse parandama ilma protsessi taaskäivitamata.

Oletame, et meil on siiski ketas vaba ruumi, kuid teises osas, näiteks /home.

Proovime meie koodi "reprogrammeerida" reaalajas.

Vaadake meie protsessi PID, mis on kogu diskiruumi ära kasutanud:

[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

Ühendame gdb kaudu protsessiga

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

Vaadake avatud failide descriptor-e:

(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

Vaadake descriptori teavet, mille number on 3, mis meid huvitab

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

Mäletades, millist süsteemi kutsungit Python kutsub (vt ülal, kus käivitasime strace ja leidsime kutsungi open), töötades meie koodi faili avamiseks, teeme me seda ise oma protsessi nimel, kuid O_WRONLY|O_CREAT|O_TRUNC bitid peame asendama numbrilise väärtusega. Selleks avame tuuma lähtekoodid, näiteks siit ja vaatame, millised lipud millegi eest vastutavad

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

Kombineerime kõik väärtused ühte, saame 00001101

Käivitame meie kutsungi gdb-st

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

Nii saime uue failideskripti numbriga 4 ja uue avatud faili teisel jaol, kontrollime:

(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

Mäletame pipe'i näidet - kuidas bash muudab faili deskriptiive ja oleme juba õppinud süsteemi kutsungi dup2.

Proovime asendada ühte faili deskriptiivi teisega

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

Kontrollime:

(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

Sulgeme faili deskriptiiv 4, kuna see ei ole meile vajalik:

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

Ja lahkume gdb-st

(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

Kontrollime uut faili:

[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

Kuidas näeme, andmed kirjutatakse uude faili, kontrollime vana:

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

Andmed ei ole kadunud, rakendus töötab, logid kirjutatakse uude kohta.

Veidi keerukam ülesanne

Kujutame ette, et andmed on meile olulised, kuid meil ei ole mingisugust ketasruumi ühelgi jaol ja me ei saa ketast ühenduda.

Mida saame teha, on suunata meie andmed kuhugi mujale, näiteks pipe'i, ning andmed pipe'ist omakorda suunata võrku mõne programmi kaudu, näiteks netcat.
Saame luua nimelise pipe'i käsuga mkfifo. See loob pseudofaili failisüsteemis, isegi kui seal pole vaba ruumi.

Taaskäivitame rakenduse ja kontrollime:

[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

Diskil ruumi pole, kuid me õnnestusime seal luua nimelise pipe:

[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

Nüüd peame leidma viisi, kuidas kõik andmed, mis satuvad sellesse pipe'i, suunata teisele serverile üle võrgu, selleks sobib sama netcat.

Serveris remote-server.example.com käivitame

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

Meie probleemse serveri peal, avame teises terminalis

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

Nüüd suunatakse kõik andmed, mis satuvad pipe'i, automaatselt stdin'i netcat'is, mis saadab need võrku sadamasse 7777.

Meie ülesanne on alustada andmete kirjutamist sellesse nimelisse pipe'i.

Meil on juba rakenduse töö:

[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

Kõigist lippudest vajame ainult O_WRONLY, kuna fail juba eksisteerib ja me ei pea seda kustutama.

[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 Okt  8 11:27 .
dr-xr-xr-x 9 user user  0 Okt  8 11:27 ..
lrwx------ 1 user user 64 Okt  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Okt  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Okt  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Okt  8 11:28 3 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 Okt  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 Okt  8 11:27 .
dr-xr-xr-x 9 user user  0 Okt  8 11:27 ..
lrwx------ 1 user user 64 Okt  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Okt  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Okt  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Okt  8 11:28 3 -> /mnt/logs/megapipe
l-wx------ 1 user user 64 Okt  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 Okt  8 11:27 .
dr-xr-xr-x 9 user user  0 Okt  8 11:27 ..
lrwx------ 1 user user 64 Okt  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Okt  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Okt  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Okt  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

Kontrollime kaugserverit remote-server.example.com

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

Andmed tulevad, kontrollime probleemse serveri staatust

[user@localhost ~]$ ls -lah /mnt/logs/
total 7.9M
drwxr-xr-x 2 user user 1.0K Okt  8 11:28 .
drwxr-xr-x 4 root     root     1.0K Okt  8 10:55 ..
-rw-rw-r-- 1 user user 7.9M Okt  8 14:17 123.txt
prw-rw-r-- 1 user user    0 Okt  8 14:22 megapipe

Andmed on säilinud, probleem on lahendatud.

Kasutan juhust ja saadan tervitused Degiro kolleegidele.
Kuulake Raadio-T podcaste.

Kõigile head soovid.

Koduseks ülesandeks pakun mõelda, mis on protsessi cat ja sleep failides deskriptorites, kui käivitada selline käsk:

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

Allikas: habr.com

Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster