Faila apraide Linux ar piemēriem

Reiz, intervijas laikā, man jautāja, ko tu darīsi, ja atklāsi, ka kāda pakalpojuma darbība nav iespējama, jo diskā beidzies vietas?

Protams, es atbildēju, ka paskatīšos, kas aizņem šo vietu, un, ja iespējams, to atbrīvošu.
Tad intervētājs jautāja, bet ko, ja nodalījumā nav brīvas vietas, bet arī failu, kas aizņem visu vietu, tu neesi redzējis?

Uz to es teicu, ka vienmēr var skatīties atvērtos failu aprakstus, piemēram, ar komandu lsof, un saprast, kura programma ir aizņēmusi visu pieejamo vietu, un tālāk rīkoties atkarībā no apstākļiem, atkarībā no tā, vai dati ir nepieciešami.

Intervētājs mani pārtrauca pēdējā vārdā, pievienojot savu jautājumu: „Pieņemsim, ka dati mums nav vajadzīgi, tas ir tikai debug žurnāls, bet programma nedarbojas, jo nevar ierakstīt debug?”

„Labi,” - atbildēju es, - „mēs varam izslēgt debug konfigurācijā un restartēt to.”
Intervētājs iebilda: „Nē, mēs nevaram restartēt programmu, mums atmiņā joprojām ir svarīgi dati, un pašam pakalpojumam ir pieslēgti svarīgi klienti, kuriem mēs nevaram prasīt atkārtoti pieslēgties.”

„Nu labi,” teicu es, „ja mēs nevaram restartēt programmu un dati mums nav svarīgi, tad mēs varam vienkārši notīrīt šo atvērtā faila aprakstu, pat ja mēs to neredzam ls komandā failu sistēmā.”

Intervētājs palika apmierināts, bet es - nē.

Tad es padomāju, kāpēc cilvēks, kas pārbauda manas zināšanas, neiedziļinās vairāk? Bet ko, ja dati tomēr ir svarīgi? Ko, ja mēs nevaram restartēt procesu, un šis process raksta uz failu sistēmu daļā, kur nav brīvas vietas? Ko, ja mēs nevaram zaudēt ne tikai jau ierakstītos datus, bet arī tos datus, kurus šis process raksta vai mēģina ierakstīt?

Tuziks

Savas karjeras sākumā mēģināju izveidot nelielu lietotni, kurā bija jāuzglabā informācija par lietotājiem. Un tad man ienāca prātā, kā sasaistīt lietotāju ar viņa datiem. Ir, piemēram, man Ivanovs Ivans Ivanovičs, un viņam ir kaut kādi dati, bet kā tos savienot? Es varu tieši norādīt, ka suns vārdā "Tuziķis" pieder šim Ivanam. Bet kas notiks, ja viņš mainīs vārdu un kļūs, piemēram, par Oliju? Tad izrādīsies, ka mūsu Oļa Ivanovna Ivanova vairs nebūs sunim, bet mūsu Tuziķis vēl joprojām būs pie neesošā Ivana. Šo problēmu atrisināja datubāze, kas katram lietotājam piešķīra unikālu identifikatoru (ID), un mans Tuziķis tika piesaistīts šim ID, kas būtībā bija vienkārši secības numurs. Līdz ar to saimnieks Tuziķim bija ID ar numuru 2, un kādu brīdi šajā ID bija Ivans, bet vēlāk šis pats ID kļuva par Oliju. Cilvēces un lauksaimniecības problēma praktiski tika atrisināta.

Faila identifikators

Faila un programmas, kas strādā ar šo failu, problēma ir apmēram tāda pati kā mūsu suns un cilvēks. Pieņemsim, ka es atvēršu failu ar nosaukumu ivan.txt un sāku tajā rakstīt vārdu tuzik, bet es paspēju ierakstīt tikai pirmo burtu "t", kad šo failu kāds pārdēvēja, piemēram, par olya.txt. Bet fails palika tas pats, un es joprojām vēlos tajā ierakstīt savu tuziķi. Katru reizi, kad es atveru failu ar sistēmas izsaukumu open jebkurā programmēšanas valodā es saņemu unikālu ID, kas norāda man uz failu, šis ID ir faila identifikators. Un pilnīgi nav svarīgi, ko un kas ar šo failu dara tālāk, to var izdzēst, to var pārdēvēt, tam var mainīt īpašnieku vai atņemt lasīšanas un rakstīšanas tiesības, es joprojām būšu tam piekļuve, jo faila atvēršanas brīdī man bija tiesības to lasīt un/vai rakstīt, un es biju paspējis sākt ar to strādāt, tāpēc man jāturpina to darīt.

Linux bibliotēka libc atver katrai palaistajai lietotnei (procesam) 3 faila identifikatorus ar numuriem 0, 1, 2. Vairāk informācijas varat atrast saites man stdio un man stdout

  • Faila identifikators 0 saucas STDIN un tiek asociēts ar datu ievadi lietotnē
  • Faila deskriptors 1 sauc STDOUT un to izmanto lietojumprogrammas datu izejai, piemēram, komandas print.
  • Faila deskriptors 2 sauc STDERR un to izmanto lietojumprogrammas datu izejai, kas ziņo par kļūdām.

Ja jūsu programmā atvērsit kādu failu lasīšanai vai rakstīšanai, jūs, visticamāk, iegūsiet pirmo brīvo ID, un tas būs numurs 3.

Faila deskriptoru sarakstu var aplūkot jebkurai procesam, ja zināt tā PID.

Piemēram, atvērsim konsoli ar bash un paskatīsimies mūsu procesa PID.

[user@localhost ]$ echo $$
15771

Otrajā konsolē palaidīsim.

[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

Faila deskriptors ar numuru 255 varat mierīgi ignorēt šajā rakstā, to jau atvēra pats bash savām vajadzībām, nevis saistītā bibliotēka.

Tagad visi 3 faila deskriptori ir saistīti ar virtualizēta termināla ierīci. /dev/pts, bet mēs joprojām varam ar tiem manipulēt, piemēram, palaidīsim otrajā konsolē.

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

Un pirmajā konsolē mēs redzēsim.

[user@localhost ]$ hello world

Pāradresācija un caurule.

Jūs varat viegli pārdefinēt šos 3 faila deskriptorius jebkurā procesā, tostarp bash, piemēram, caur cauruli, kas savieno divus procesus, skatāmies.

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

Jūs paši varat palaist šo komandu ar. strace -f un redzēt, kas notiek iekšienē, bet es īsumā pastāstīšu.

Mūsu vecāku process bash ar PID 15771 analizē mūsu komandu un saprot, cik tieši komandas mēs vēlamies palaist, mūsu gadījumā tās ir divas: cat un sleep. Bash zina, ka tam jāizveido divi bērnu procesi un jāapvieno tie ar vienu cauruli. Tātad basha prasīs 2 bērnu procesus un vienu cauruli.

Pirms bērnu procesu izveides bash uzsāk sistēmas izsaukumu. pipe un iegūst jaunus faila deskriptorius uz pagaidu buferi caurules, bet šis buferis nekādā veidā vēl nesavieno mūsu divus bērnu procesus.

Vecāku procesam tas izskatās tā, it kā caurule jau pastāvētu, bet bērnu procesu vēl nav:

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

Pēc tam ar sistēmas izsaukumu clone bash izveido divus apakšprocesus, un mūsu trīs procesi izskatīsies šādi:

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

Neaizmirstiet, ka clone klonē procesu kopā ar visām failu aprakstošām datnēm, tāpēc vecā procesā un apakšprocesos tās būs vienādas. Vecā procesa ar PID 15771 uzdevums ir uzraudzīt apakšprocesus, tāpēc tas vienkārši gaida atbildi no apakšprocesiem.

Tāpēc pipe nav nepieciešams, un tas aizver failu aprakstošās datnes ar numuriem 3 un 4.

Pirmajā apakšprocesā bash ar PID 9004, izmantojot sistēmas izsaukumu dup2, maina mūsu STDOUT failu aprakstošo datni ar numuru 1 uz failu aprakstošu datni, kas norāda uz pipe, mūsu gadījumā tas ir numurs 3. Tādējādi viss, ko pirmais apakšprocess ar PID 9004 rakstīs STDOUT, automātiski nonāks pipe buferī.

Otrajā apakšprocesā ar PID 9005 bash, izmantojot dup2, maina failu aprakstošo datni STDIN ar numuru 0. Tagad viss, ko mūsu otrais bash ar PID 9005 lasīs, tiks lasīts no pipe.

Pēc tam apakšprocesos arī slēdz failu aprakstošās datnes ar numuriem 3 un 4, jo tās vairs netiek izmantotas.

Failu aprakstošo datni 255 es apzināti ignorēju, to izmanto iekšējām vajadzībām pats bash un apakšprocesos tā arī tiks aizvērta.

Tālāk pirmajā apakšprocesā ar PID 9004 bash ar sistēmas izsaukumu exec izpilda izpildāmo failu, ko mēs norādījām komandrindā, mūsu gadījumā tas ir /usr/bin/cat.

Otrajā apakšprocesā ar PID 9005 bash izmanto otro izpildāmo failu, ko mēs norādījām, mūsu gadījumā tas ir /usr/bin/sleep.

Sistēmas izsaukums exec neaizver failu aprakstītājus, ja tie nav atvērti ar O_CLOEXEC karogu, izsaucot open. Mūsu gadījumā pēc izpildāmo failu palaišanas visi pašreizējie failu aprakstītāji tiks saglabāti.

Pārbaudām konsolē:

[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

Kā redzat, unikālais numurs mūsu caurulei abos procesos sakrīt. Tādējādi mums ir saikne starp diviem dažādiem procesiem ar vienu vecāku.

Tiem, kas nav pazīstami ar sistēmas izsaukumiem, ko izmanto bash, ļoti iesaku palaist komandas caur strace un paskatīties, kas notiek iekšēji, piemēram, tādā veidā:

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

Atgriezīsimies pie mūsu problēmas ar diskā trūkstošo vietu un mēģinājumu saglabāt datus bez procesa pārstartēšanas. Uzrakstīsim nelielu programmu, kas rakstīs diskā aptuveni 1 megabaitu sekundē. Ja kādu iemeslu dēļ nevarēsim ierakstīt datus diskā, mēs vienkārši ignorēsim to un mēģināsim rakstīt datus atkārtoti pēc sekundes. Piemērā es izmantoju Python, jūs varat izmantot jebkuru citu programmēšanas valodu.

[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

Palaižam programmu un paskatāmies uz failu aprakstītājiem

[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

Kā redzam, mums ir trīs standarta failu descriptori un vēl viens, ko mēs atvērum. Pārbaudīsim faila lielumu:

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

dati tiek rakstīti, mēģinām mainīt faila tiesības:

[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

Redzam, ka dati joprojām tiek rakstīti, lai gan mūsu lietotājam nav tiesību rakstīt failā. Mēģināsim to izdzēst:

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

Kur rakstās dati? Un vai tie vispār tiek rakstīti? Pārbaudām:

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

Jā, mūsu faila deskriptsors joprojām pastāv, un mēs varam strādāt ar šo faila deskriptsoru kā ar mūsu veco failu, mēs varam to lasīt, notīrīt un kopēt.

Paskatāmies uz faila izmēru:

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

Faila izmērs 19923457. Mēģinām notīrīt failu:

[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

Kā redzam, faila izmērs tikai palielinās un mūsu truncētā komanda neizdevās. Vēršamies pie dokumentācijas par sistēmas izsaukumiem. open. Ja, atverot failu, mēs izmantojam O_APPEND flagu, tad pie katras rakstīšanas operētājsistēma pārbauda faila izmēru un ieraksta datus faila beigās, un to veic atomāri. Tas ļauj vairākiem pavedieniem vai procesiem rakstīt uz vienu un to pašu failu. Bet mūsu kodā mēs neizmantojam šo flagu. Mēs varam redzēt citu faila izmēru lsof pēc truncētās komandas, tikai ja mēs atvērsim failu papildu rakstīšanai, tāpēc mūsu kodā vietā

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

mums būtu jānomaina uz

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

Pārbaudām ar ‘w’ flagu

[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

un ar «a» karogu

[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

Programmējot jau palaistu procesu

Bieži programmētāji, izstrādājot un testējot programmas, izmanto debuggerus (piemēram, GDB) vai dažādus reģistrēšanas līmeņus lietojumprogrammā. Linux nodrošina iespēju faktiski rakstīt un mainīt jau palaistu programmu, piemēram, mainīt mainīgo vērtības, uzstādīt pārtraukuma punktus u.t.t.

Atgriežoties pie sākotnējā jautājuma par nepietiekamo vietu diskā, lai ierakstītu failu, mēģināsim simulēt problēmu.

Izveidosim failu mūsu daļai, kuru pievienosim kā atsevišķu disku:

[user@localhost ~]$ dd if=/dev/zero of=~/tempfile_for_article.dd bs=1M count=10
10+0 ieraksti iekšā
10+0 ieraksti ārā
10485760 baitus (10 MB) kopēts, 0.00525929 s, 2.0 GB/s
[user@localhost ~]$

Izveidosim failu sistēmu:

[user@localhost ~]$ mkfs.ext4 ~/tempfile_for_article.dd
mke2fs 1.42.9 (28-Dec-2013)
/home/user/tempfile_for_article.dd nav blokmodes īpašais ierīce.
Turpināt tāpat? (y,n) y
...
Rakstīšana superbloku un failu sistēmas uzskaites informāciju: pabeigta
[user@localhost ~]$

Pievienosim failu sistēmu:

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

Izveidojam katalogu ar mūsu īpašnieku:

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

Atvērsim failu tikai rakstīšanai mūsu programmā:

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

Sāksim

[user@localhost ]$ python openforwrite.py 

Gaidām dažas sekundes

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

Tātad, mēs saņēmām problēmu, kas aprakstīta šīs raksta sākumā. Brīvās vietas 0, aizņemtās 100%.

Mēs atceramies, ka uzdevuma noteikumos mēs mēģinām ierakstīt ļoti svarīgus datus, kurus nevar pazaudēt. Un tajā pašā laikā mums jāatjauno pakalpojums bez procesa restartēšanas.

Pieņemsim, ka mums tomēr ir vieta diskā, bet citā nodalījumā, piemēram, /home.

Mēģināsim "pārrakstīt kodu gaisā".

Skatāmies mūsu procesa PID, kurš patērēja visu vietu diskā:

[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

Pievienojamies procesam caur gdb

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

Skatāmies atvērtos failu deskriptorius:

(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

Skatāmies informāciju par failu deskriptoru ar numuru 3, kas mūs interesē

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

Atceroties, kādu sistēmas izsaukumu veic Python (skatiet augstāk, kur mēs palaidām strace un atradām izsaukumu open), apstrādājot mūsu kodu faila atvēršanai, mēs paši darām to pašu mūsu procesa vārdā, bet O_WRONLY|O_CREAT|O_TRUNC biti mums vajag aizstāt ar ciparu vērtību. Tāpēc atveram kodu avotus, piemēram, šeit un skatāmies, kādiem mērķiem katrs no karogiem kalpo.

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

Apvienojam visas vērtības vienā, iegūstam 00001101.

Palaidām mūsu izsaukumu no gdb.

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

Tātad mēs ieguvām jaunu faila deskriptoru ar numuru 4 un jaunu atvērtu failu citā nodalījumā, pārbaudām:

(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ēs atceramies piemēru ar pipe — kā bash maina faila deskriptorus, un jau iemācījāmies sistēmas izsaukumu dup2.

Mēģinām aizstāt vienu faila deskriptoru ar citu.

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

Pārbaudām:

(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

Aizveram faila deskriptoru 4, jo tas mums nav vajadzīgs:

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

Un iznākam no 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

Pārbaudām jauno failu:

[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

Kā redzam, dati tiek pierakstīti jaunajā failā, pārbaudām veco:

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

Dati nav zuduši, programma strādā, logi tiek pierakstīti jaunā vietā.

Nedaudz sarežģīsim uzdevumu.

Iedomāsimies, ka dati mums ir svarīgi, bet vietas diskā nav nevienā no nodalījumiem un mēs nevaram pievienot disku.

Ko mēs varam izdarīt, ir novirzīt savus datus kaut kur, piemēram, uz pipe, un datus no pipe savukārt novirzīt tīklā caur kādu programmu, piemēram, netcat.
Mēs varam izveidot nosaukto pipe ar komandu mkfifo. Tā izveidos pseidofailu failu sistēmā, pat ja tajā nav brīvas vietas.

Pārlādējam lietotni un pārbaudām:

[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

Vietas diskā nav, bet mēs veiksmīgi izveidojam nosaukto cauruli:

[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

Tagad mums jāpārliek visi dati, kas nonāk šajā caurulē, uz citu serveri caur tīklu, tam būs noderīgs tas pats netcat.

Uz servera remote-server.example.com palaidīsim

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

Mūsu problēmu serverī uzsāksim atsevišķā terminālā

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

Tagad visi dati, kas nonāks caurulē, automātiski nokļūs standarta ievades netcat, kas tos nosūtīs tīklā uz portu 7777.

Viss, kas mums jādara, ir sākt rakstīt mūsu datus šajā nosauktajā caurulē.

Mums jau ir palaista lietotne:

[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

No visiem karogiem mums nepieciešams tikai O_WRONLY, jo fails jau pastāv un mums nav jātīra to.

[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

Pārbaudām attālināto serveri remote-server.example.com

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

Dati tiek sūtīti, pārbaudām problēmu ar serveri

[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

Dati ir saglabāti, problēma ir atrisināta.

Izmantojot šo iespēju, sūtu sveicienus kolēģiem no uzņēmuma Degiro.
Klausieties Radio-T podkāstus.

Viss labais.

Kā mājas darbu piedāvāju padomāt, kas notiks procesa failu deskriptoros, ja izpildīsiet šādu komandu:

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

Avots: habr.com

Iegādājieties uzticamu tīmekļa hostingu ar DDoS aizsardzību, VPS VDS serveriem 🔥 Iegādājieties uzticamu tīmekļa hostingu ar DDoS aizsardzību, VPS VDS serveriem | ProHoster