„Linux“ failų aprašas su pavyzdžiais

Kartą per pokalbį manęs paklausė, ką darysite, jei pastebėsite, kad paslauga neveikia dėl to, kad diske pritrūko vietos?

Aišku, atsakiau, kad pažiūrėsiu, ką užima ši vieta ir, esant galimybei, tą vietą išvalysiu.
Tada pašnekovas paklausė, o jeigu skaidinyje nėra laisvos vietos, bet taip pat nematote failų, kurie užimtų visą vietą?

Į tai aš sakiau, kad visada galite pažvelgti į atidarytų failų aprašus, pavyzdžiui, su komanda lsof, ir suprasti, kuri programa užėmė visą laisvą vietą, o tada galite veikti pagal aplinkybes, priklausomai nuo to, ar reikia duomenų. .

Pašnekovas pertraukė mane paskutiniu žodžiu, papildydamas savo klausimą: „Tarkime, mums nereikia duomenų, tai tik derinimo žurnalas, bet programa neveikia, nes negali parašyti derinimo“?

„Gerai, – atsakiau, – galime išjungti derinimą programos konfigūracijoje ir paleisti iš naujo.
Pašnekovas paprieštaravo: „Ne, negalime iš naujo paleisti programos, atmintyje vis dar saugome svarbius duomenis, o svarbūs klientai yra prijungti prie pačios paslaugos, kurios negalime priversti vėl prisijungti“.

„Gerai“, – pasakiau, „jei negalime iš naujo paleisti programos ir duomenys mums nėra svarbūs, galime tiesiog išvalyti šį atidarytą failą naudodami failo deskriptorių, net jei jo nematome komandoje ls failų sistemoje“.

Pašnekovas buvo patenkintas, o aš – ne.

Tada pagalvojau, kodėl žmogus, tikrinantis mano žinias, nesigilina? Bet ką daryti, jei duomenys vis dėlto svarbūs? Ką daryti, jei negalime paleisti proceso iš naujo, o procesas rašo į failų sistemą skaidinyje, kuriame nėra laisvos vietos? Ką daryti, jei negalime prarasti ne tik jau įrašytų duomenų, bet ir duomenų, kuriuos šis procesas rašo ar bando įrašyti?

Tuzik

Karjeros pradžioje bandžiau sukurti nedidelę programą, kurioje būtų saugoma vartotojo informacija. Tada pagalvojau, kaip priderinti vartotoją prie jo duomenų. Pavyzdžiui, aš turiu Ivanovą Ivaną Ivanovičių ir jis turi tam tikros informacijos, bet kaip man su jais susidraugauti? Galiu tiesiogiai pabrėžti, kad šuo vardu Tuzik priklauso būtent šiam Ivanui. O kas, jei jis pakeis vardą ir vietoj Ivano taps, pavyzdžiui, Olya? Tada paaiškės, kad mūsų Olya Ivanovna Ivanova nebeturės šuns, o mūsų Tuzikas vis tiek priklausys neegzistuojančiam Ivanui. Duomenų bazė, kuri kiekvienam vartotojui suteikė unikalų identifikatorių (ID), padėjo išspręsti šią problemą, o mano Tuzik buvo susietas su šiuo ID, kuris iš tikrųjų buvo tik serijos numeris. Taigi, tūzo savininkas turėjo ID numerį 2, o tam tikru metu Ivanas turėjo šį ID, o tada Olya tapo tuo pačiu ID. Žmonijos ir gyvulininkystės problema buvo praktiškai išspręsta.

Failo aprašas

Failo ir su šiuo failu veikiančios programos problema yra maždaug tokia pati kaip mūsų šuns ir žmogaus problema. Tarkime, aš atidariau failą pavadinimu ivan.txt ir pradėjau į jį rašyti žodį tuzik, bet sugebėjau įrašyti tik pirmąją raidę „t“, o šį failą kažkas pervadino, pavyzdžiui, į olya.txt. Bet failas išlieka tas pats, ir aš vis tiek noriu jame įrašyti savo tūzą. Kiekvieną kartą, kai failas atidaromas sistemos iškvietimu atidaryti bet kuria programavimo kalba gaunu unikalų ID, kuris nukreipia mane į failą, šis ID yra failo aprašas. Ir visai nesvarbu ką ir kas toliau daro su šiuo failu, jį galima ištrinti, pervardyti, pakeisti savininką arba atimti skaitymo ir rašymo teises, aš vis tiek turėsiu prieigą prie jo, nes failo atidarymo metu turėjau teises jį skaityti ir/ar rašyti ir man pavyko pradėti su juo dirbti, vadinasi, privalau tai daryti ir toliau.

Linux sistemoje libc biblioteka atidaro 3 deskriptorių failus kiekvienai veikiančiai programai (procesui), sunumeruotus 0,1,2. Daugiau informacijos rasite nuorodose vyras stdio и vyras stdout

  • Failo deskriptorius 0 vadinamas STDIN ir yra susietas su programos įvestimi
  • 1 failo deskriptorius vadinamas STDOUT ir jį naudoja programos duomenims, pvz., spausdinimo komandoms, išvesti
  • 2 failo aprašas vadinamas STDERR ir jį naudoja programos, kad išvestų klaidų pranešimus.

Jei savo programoje atidarote bet kurį failą skaitymui ar rašymui, greičiausiai gausite pirmąjį nemokamą ID ir jis bus 3 numeris.

Failų deskriptorių sąrašą galima peržiūrėti bet kuriam procesui, jei žinote jo PID.

Pavyzdžiui, atidarykime bash konsolę ir pažiūrėkime į mūsų proceso PID

[user@localhost ]$ echo $$
15771

Antroje konsolėje paleiskite

[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

Šiame straipsnyje galite saugiai nepaisyti failo deskriptoriaus 255; jį savo poreikiams atidarė pats bash, o ne susieta biblioteka.

Dabar visi 3 deskriptorių failai yra susieti su pseudo terminalo įrenginiu /dev/pts, bet vis tiek galime jais manipuliuoti, pavyzdžiui, paleisti juos antroje konsolėje

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

Ir pirmoje konsolėje pamatysime

[user@localhost ]$ hello world

Peradresavimas ir vamzdis

Galite lengvai nepaisyti šių 3 deskriptorių failų bet kuriame procese, įskaitant bash, pavyzdžiui, per vamzdį, jungiantį du procesus, žr.

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

Šią komandą galite paleisti patys strace -f ir pažiūrėk, kas vyksta viduje, bet papasakosiu trumpai.

Mūsų pirminis bash procesas su PID 15771 analizuoja mūsų komandą ir tiksliai supranta, kiek komandų norime paleisti, mūsų atveju jų yra dvi: katė ir miegas. Bash žino, kad reikia sukurti du antrinius procesus ir sujungti juos į vieną vamzdį. Iš viso bash reikės 2 vaikų procesų ir vieno vamzdžio.

Prieš kurdamas antrinius procesus, „Bash“ vykdo sistemos iškvietimą vamzdis ir gauna naujus failų deskriptorius laikinajame vamzdžio buferyje, tačiau šis buferis dar nesujungia mūsų dviejų antrinių procesų.

Atrodo, kad pirminio proceso vamzdis jau yra, bet antrinių procesų dar nėra:

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

Tada naudokite sistemos skambutį klonuoti bash sukuria du antrinius procesus, o mūsų trys procesai atrodys taip:

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

Nepamirškite, kad klonas klonuoja procesą kartu su visais failų aprašais, todėl pirminiame ir antriniame procese jie bus vienodi. Pirminio proceso su PID 15771 užduotis yra stebėti antrinius procesus, todėl jis tiesiog laukia atsakymo iš vaikų.

Todėl jai nereikia vamzdžio ir jis uždaro failų aprašus, pažymėtus 3 ir 4.

Pirmajame antriniame bash procese su PID 9004 sistemos iškvietimas dup2, pakeičia mūsų STDOUT failo deskriptorių numerį 1 į failo deskriptorių, nurodantį vamzdį, mūsų atveju jis yra 3. Taigi viskas, ką pirmasis antrinis procesas su PID 9004 įrašo į STDOUT, automatiškai pateks į vamzdžio buferį.

Antrajame antriniame procese su PID 9005, bash naudoja dup2, kad pakeistų failo deskriptorių STDIN numerį 0. Dabar viskas, ką skaitys mūsų antrasis bash su PID 9005, bus nuskaitytas iš vamzdžio.

Po to 3 ir 4 failų aprašai taip pat uždaromi antriniuose procesuose, nes jie nebenaudojami.

Sąmoningai ignoruoju failo deskriptorių 255; jį vidiniais tikslais naudoja pats bash ir jis taip pat bus uždarytas antriniuose procesuose.

Tada pirmame antriniame procese su PID 9004 bash pradeda naudoti sistemos iškvietimą exec vykdomasis failas, kurį nurodėme komandinėje eilutėje, mūsų atveju tai yra /usr/bin/cat.

Antrame antriniame procese su PID 9005, bash paleidžia antrąjį mūsų nurodytą vykdomąjį failą, mūsų atveju /usr/bin/sleep.

Exec sistemos iškvietimas neuždaro failų rankenėlių, nebent jos buvo atidarytos su O_CLOEXEC vėliava tuo metu, kai buvo atidarytas skambutis. Mūsų atveju, paleidus vykdomuosius failus, visi esami failų aprašai bus išsaugoti.

Patikrinkite konsolėje:

[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

Kaip matote, unikalus mūsų vamzdžio numeris abiejuose procesuose yra vienodas. Taigi mes turime ryšį tarp dviejų skirtingų procesų su tuo pačiu tėvu.

Tiems, kurie nėra susipažinę su sistemos iškvietimais, kuriuos naudoja bash, labai rekomenduoju paleisti komandas per strace ir pamatyti, kas vyksta viduje, pavyzdžiui, taip:

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

Grįžkime prie problemos, susijusios su mažai vietos diske ir bandymu išsaugoti duomenis nepaleidus proceso iš naujo. Parašykime nedidelę programą, kuri į diską įrašys maždaug 1 megabaitą per sekundę. Be to, jei dėl kokių nors priežasčių negalėjome įrašyti duomenų į diską, mes tiesiog ignoruosime tai ir bandysime įrašyti duomenis iš naujo po sekundės. Pavyzdyje aš naudoju Python galite naudoti bet kurią kitą programavimo kalbą.

[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

Paleiskite programą ir pažiūrėkime į failų aprašus

[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

Kaip matote, turime 3 standartinius failų aprašus ir dar vieną, kurį atidarėme. Patikrinkime failo dydį:

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

Duomenys rašomi, bandome pakeisti failo teises:

[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

Matome, kad duomenys vis dar rašomi, nors mūsų vartotojas neturi leidimo rašyti į failą. Pabandykime jį pašalinti:

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

Kur įrašyti duomenys? Ir ar jie apskritai parašyti? Mes tikriname:

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

Taip, mūsų failo deskriptorius vis dar egzistuoja ir šį failo deskriptorių galime traktuoti kaip seną failą, galime jį perskaityti, išvalyti ir kopijuoti.

Pažiūrėkime į failo dydį:

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

Failo dydis yra 19923457. Pabandykime išvalyti failą:

[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

Kaip matote, failo dydis tik didėja, o mūsų bagažinė neveikė. Pažiūrėkime į sistemos iškvietimo dokumentaciją atidaryti. Jei atidarydami failą naudosime O_APPEND vėliavėlę, tada su kiekvienu įrašymu operacinė sistema patikrina failo dydį ir įrašo duomenis į patį failo galą ir tai daro atomiškai. Taip į tą patį failą gali įrašyti kelios gijos arba procesai. Tačiau savo kode šios vėliavėlės nenaudojame. Kitokį failo dydį lsof galime matyti po kamieno tik tada, kai atidarome failą papildomam rašymui, o tai reiškia, kad vietoj to mūsų kode

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

turime įdėti

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

Tikrinama su „w“ vėliavėle

[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

ir su „a“ vėliava

[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

Jau vykdomo proceso programavimas

Dažnai programuotojai kurdami ir testuodami programas naudoja derintuvus (pvz., GDB) arba įvairius prisijungimo lygius programoje. „Linux“ suteikia galimybę iš tikrųjų rašyti ir keisti jau veikiančią programą, pavyzdžiui, pakeisti kintamųjų reikšmes, nustatyti pertraukos tašką ir pan.

Grįžtant prie pradinio klausimo, kad diske nepakanka vietos failui įrašyti, pabandykime imituoti problemą.

Sukurkime failą savo skaidiniui, kurį prijungsime kaip atskirą diską:

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

Sukurkime failų sistemą:

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

Prijunkite failų sistemą:

[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

Su savininku sukuriame katalogą:

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

Atidarykime failą rašyti tik mūsų programoje:

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

Paleisti

[user@localhost ]$ python openforwrite.py 

Laukiame kelias sekundes

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

Taigi, turime šio straipsnio pradžioje aprašytą problemą. Laisva vieta 0, 100% užimta.

Prisimename, kad pagal užduoties sąlygas stengiamės įrašyti labai svarbius duomenis, kurių negalima prarasti. Ir tuo pat metu turime pataisyti paslaugą nepaleidę proceso iš naujo.

Tarkime, kad vis dar turime vietos diske, bet kitame skaidinyje, pavyzdžiui, /home.

Pabandykime „perprogramuoti“ savo kodą.

Pažvelkime į mūsų proceso PID, kuris suvalgė visą disko vietą:

[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

Prisijunkite prie proceso per gdb

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

Pažvelkime į atidarytų failų aprašus:

(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

Mes žiūrime į mus dominančią informaciją apie failo aprašą Nr. 3

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

Turėdami omenyje, kokį sistemos iškvietimą atlieka Python (žr. aukščiau, kur paleidome strace ir radome atvirą iškvietimą), apdorodami savo kodą, kad atidarytume failą, tą patį darome patys savo proceso vardu, tačiau mums reikia O_WRONLY|O_CREAT| O_TRUNC bitai pakeičiami skaitine reikšme. Norėdami tai padaryti, atidarykite, pavyzdžiui, branduolio šaltinius čia ir pažiūrėkite, kurios vėliavos už ką atsakingos

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

Visas reikšmes sujungiame į vieną, gauname 00001101

Mes skambiname iš gdb

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

Taigi gavome naują failo aprašą su numeriu 4 ir naują atidarytą failą kitame skaidinyje, patikriname:

(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

Prisimename pavyzdį su pipe – kaip bash keičia failų aprašus, ir jau išmokome dup2 sistemos iškvietimą.

Bandome vieną failo aprašą pakeisti kitu

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

Mes patikriname:

(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

Uždarome 4 failo aprašą, nes mums jo nereikia:

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

Ir išeiti iš 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

Naujo failo tikrinimas:

[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

Kaip matote, duomenys įrašomi į naują failą, patikrinkime senąjį:

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

Duomenų neprarandama, programa veikia, žurnalai įrašomi į naują vietą.

Šiek tiek apsunkinkime užduotį

Įsivaizduokime, kad duomenys mums svarbūs, bet neturime vietos diske nė viename skaidinyje ir negalime prijungti disko.

Ką mes galime padaryti, tai nukreipti savo duomenis kur nors, pavyzdžiui, į vamzdį, o savo ruožtu nukreipti duomenis iš vamzdžio į tinklą per kokią nors programą, pavyzdžiui, netcat.
Su mkfifo komanda galime sukurti pavadintą vamzdį. Jis sukurs pseudo failą failų sistemoje, net jei joje nėra laisvos vietos.

Iš naujo paleiskite programą ir patikrinkite:

[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

Vietos diske nėra, bet sėkmingai sukuriame pavadintą vamzdį:

[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

Dabar turime kažkaip suvynioti visus į šį vamzdį patenkančius duomenis per tinklą į kitą serverį; tam tinka tas pats „netcat“.

Mes paleidžiame serveryje remote-server.example.com

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

Probleminiame serveryje paleidžiame atskirame terminale

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

Dabar visi duomenys, kurie patenka į vamzdį, automatiškai pateks į stdin in netcat, kuris nusiųs juos į tinklą per 7777 prievadą.

Viskas, ką turime padaryti, tai pradėti rašyti savo duomenis į šį pavadintą vamzdį.

Jau veikia programa:

[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

Iš visų vėliavėlių mums reikia tik O_WRONLY, nes failas jau yra ir mums jo išvalyti nereikia

[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

Tikrinamas nuotolinis serveris remote-server.example.com

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

Duomenys ateina, mes patikriname probleminį serverį

[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

Duomenys išsaugoti, problema išspręsta.

Naudojuosi proga ir pasisveikinau su savo kolegomis iš Degiro.
Klausykitės „Radio-T“ podcast'ų.

Gera visiems.

Kaip namų darbus siūlau pagalvoti, kas bus proceso failo aprašuose cat and sleep, jei paleisite šią komandą:

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

Šaltinis: www.habr.com

Добавить комментарий