Odată, la un interviu, m-a întrebat cineva ce aș face dacă aș descoperi un serviciu care nu funcționează din cauza lipsei de spațiu pe disc.
Desigur, am răspuns că voi verifica ce ocupă acest spațiu și, dacă este posibil, voi elibera loc.
Atunci, intervievatorul m-a întrebat ce aș face dacă nu ar fi liber spațiu pe partition, dar nici fișiere care să ocupe tot spațiul nu le-aș vedea.
La aceasta, am spus că întotdeauna pot verifica descriptorii de fișiere deschise, de exemplu cu comanda lsof, și să înțeleg care aplicație a ocupat tot spațiul disponibil, iar apoi putem acționa în funcție de circumstanțe, în funcție de necesitatea datelor.
Intervievatorul m-a întrerupt la ultimul cuvânt, completând întrebarea sa: „Să presupunem că datele nu ne sunt necesare, este doar un log de debugging, dar aplicația nu funcționează pentru că nu poate scrie logul de debugging?”
„Ok”, am răspuns, „putem dezactiva debugging-ul în configurația aplicației și să o repornim.”
Intervievatorul a obiectat: „Nu, aplicația nu o putem reporni, avem în memorie date importante, iar la serviciu sunt conectați clienți importanți, pe care nu putem să-i obligăm să se reconecteze.”
„Bine”, am spus eu, „dacă nu putem reporni aplicația și datele nu ne sunt importante, putem pur și simplu să eliberăm acest fișier deschis prin descriptorul de fișiere, chiar dacă nu-l vedem cu comanda ls în sistemul de fișiere.”
Intervievatorul a rămas mulțumit, eu nu.
Atunci m-am gândit de ce persoana care îmi verifică cunoștințele nu caută mai în profunzime? Ce se întâmplă dacă datele sunt totuși importante? Ce dacă nu putem reporni procesul, iar acest proces scrie pe sistemul de fișiere pe o partiție unde nu mai este loc liber? Ce se întâmplă dacă nu putem pierde doar datele deja scrise, ci și cele pe care acest proces le scrie sau încearcă să le scrie?
Tuzik
La începutul carierei mele, am încercat să creez o aplicație mică în care trebuia să stochez informații despre utilizatori. M-am întrebat cum să corelez utilizatorul cu datele sale. De exemplu, am un Ivanov Ivan Ivaș și are anumite date, dar cum să le asociez? Pot specifica direct că câinele numit „Tuzik” aparține acestui Ivan. Dar ce se întâmplă dacă își schimbă numele și devine, de exemplu, Olya? Atunci, Olya Ivanovna Ivanova nu va mai avea câine, iar Tuzik va aparține unui Ivan inexistent. Această problemă a fost rezolvată cu ajutorul unei baze de date, care oferea fiecărui utilizator un identificator unic (ID), iar Tuzik era asociat cu acest ID, care, de fapt, era doar un număr secvențial. Astfel, stăpânul lui Tuzik avea ID-ul cu numărul 2, și la un moment dat era Ivan, iar apoi, cu același ID, a devenit Olya. Problema umanității și a creșterii animalelor a fost practic rezolvată.
Descriptor de fișier
Problema fișierului și a programului care lucrează cu acest fișier este destul de asemănătoare cu cea dintre câinele nostru și om. Să presupunem că am deschis un fișier numit ivan.txt și am început să scriu cuvântul tuzik, dar am reușit să scriu doar prima literă „t” în fișier, iar acest fișier a fost redenumit de cineva, de exemplu, în olya.txt. Dar fișierul a rămas același, iar eu încă vreau să scriu despre câinele meu Tuzik. De fiecare dată când deschid fișierul printr-o apelare sistemică în orice limbaj de programare, primesc un ID unic care îmi indică fișierul, acest ID este descriptorul de fișier. Și nu are importanță ce și cine face cu acest fișier mai departe; acesta poate fi șters, poate fi redenumit, proprietarul său poate fi schimbat sau i se pot revoca drepturile de citire și scriere, eu voi avea în continuare acces la el, deoarece în momentul deschiderii fișierului am avut drepturile pentru a-l citi și/sau scrie și am început să lucrez cu el, așa că trebuie să continui să fac acest lucru.
În Linux, biblioteca libc deschide pentru fiecare aplicație (proces) în execuție 3 descriptoare de fișier, cu numerele 0, 1, 2. Mai multe informații puteți găsi la linkurile și
- Descriptorul de fișier 0 se numește STDIN și este asociat cu introducerea de date în aplicație.
- Descriptorul de fișier 1 se numește STDOUT și este utilizat de aplicații pentru a ieși date, de exemplu, comanda print.
- Descriptorul de fișier 2 se numește STDERR și este utilizat de aplicații pentru a ieși date care raportează erori.
Dacă în programul dvs. deschideți orice fișier pentru citire sau scriere, este foarte probabil că veți obține primul ID liber și acesta va fi numărul 3.
Lista descriptorilor de fișiere poate fi consultată pentru orice proces, dacă cunoașteți PID-ul acestuia.
De exemplu, să deschidem consola cu bash și să verificăm PID-ul procesului nostru.
[user@localhost ]$ echo $$
15771
În a doua consolă vom rula
[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
Descriptorul de fișier cu numărul 255 îl puteți ignora cu încredere în cadrul acestui articol, deoarece a fost deschis pentru nevoile sale de către bash-ul în sine, nu de o bibliotecă încorporată.
Acum toate cele 3 descriptoare de fișiere sunt legate de un dispozitiv pseudo-terminal. , dar putem să le manipulăm, de exemplu să rulăm în a doua consolă
[user@localhost ]$ echo "hello world" > /proc/15771/fd/0
Și în prima consolă vom vedea
[user@localhost ]$ hello world
Redirect și Pipe
Puteți redefini cu ușurință aceste 3 descriptoare de fișiere în orice proces, inclusiv în bash, de exemplu printr-un pipe care leagă două procese, să vedem
[user@localhost ]$ cat /dev/zero | sleep 10000
Puteți rula această comandă cu strace -f și să observați ce se întâmplă în interior, dar voi explica pe scurt.
Procesul nostru părinte bash cu PID 15771 analizează comanda noastră și înțelege câte comenzi dorim să rulăm, în cazul nostru două: cat și sleep. Bash știe că trebuie să creeze două procese fiice și să le unească printr-un pipe. Astfel, bash va necesita 2 procese fiice și un pipe.
Înainte de a crea procesele fiice, bash efectuează un apel de sistem și obține noi descriptoare de fișiere pentru un buffer temporar de pipe, dar acest buffer nu leagă încă cele două procese fiice.
Pentru procesul părinte, arată ca și cum pipe-ul ar exista deja, iar procesele fiice încă nu sunt.
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
Apoi, prin apelul sistemului bash creează două procese copil, iar cele trei procese ale noastre vor arăta astfel:
PID comanda
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 comanda
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 comanda
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
Să nu uităm că clone clonează procesul împreună cu toate descriptorii de fișiere, astfel încât în procesul părinte și în cele copil vor fi identici. Sarcina procesului părinte cu PID 15771 este să monitorizeze procesele copil, așa că așteaptă pur și simplu răspuns de la cele copil.
Prin urmare, pipe-ul nu îi este necesar, așa că închide descriptorii de fișiere cu numerele 3 și 4.
În primul proces copil bash cu PID 9004, apelul sistemului , schimbă descriptorul de fișier STDOUT cu numărul 1 în descriptorul de fișier care indică către pipe, în cazul nostru, numărul 3. Astfel, tot ceea ce primul proces copil cu PID 9004 va scrie în STDOUT, va ajunge automat în buffer-ul pipe.
În cel de-al doilea proces copil cu PID 9005, bash schimbă prin dup2 descriptorul de fișier STDIN cu numărul 0. Acum, tot ceea ce va citi cel de-al doilea bash cu PID 9005, va citi din pipe.
După aceasta, în procesele copil se închid de asemenea descriptorii de fișiere cu numerele 3 și 4, deoarece nu mai sunt utilizați.
Descriptorul de fișier 255 îl ignor intenționat, el este folosit pentru nevoile interne ale bash-ului și în procesele copil va fi de asemenea închis.
În continuare, în primul proces copil cu PID 9004, bash lansează, prin apelul sistemului fișierul executabil pe care l-am specificat în linia de comandă, în cazul nostru, acesta este /usr/bin/cat.
În cel de-al doilea proces copil cu PID 9005, bash lansează al doilea fișier executabil pe care l-am specificat, în cazul nostru, acesta este /usr/bin/sleep.
Apelul sistemului exec nu închide descriptorii de fișiere dacă nu au fost deschiși cu flagul O_CLOEXEC în timpul apelului open. În cazul nostru, după ce sunt executate fișierele, toți descriptorii de fișiere curenți vor rămâne deschiși.
Verificăm în consolă:
[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
După cum puteți observa, numărul unic al pipe-ului nostru este același în ambele procese. Astfel, avem o legătură între două procese diferite cu un singur părinte.
Pentru cei care nu sunt familiarizați cu apelurile de sistem folosite de bash, recomand cu tărie să rulați comenzi prin strace și să observați ce se întâmplă în interior, de exemplu:
strace -s 1024 -f bash -c "ls | grep hello"
Să ne întoarcem la problema noastră cu lipsa de spațiu pe disc și încercarea de a salva datele fără a reporni procesul. Vom scrie un mic program care va scrie pe disc aproximativ 1 megabyte pe secundă. Dacă, din diverse motive, nu am reușit să scriem datele pe disc, vom ignora pur și simplu acest lucru și vom încerca să scriem din nou datele după o secundă. În exemplul meu folosesc Python, dar puteți folosi orice alt limbaj de programare.
[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
Să rulăm programul și să ne uităm la descriptorii de fișiere.
[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
După cum vedem, avem cei 3 descriitori standard de fișiere și încă unul, pe care l-am deschis. Să verificăm dimensiunea fișierului:
[user@localhost ]$ ls -lah 123.txt
-rw-rw-r-- 1 user user 117M Oct 7 16:30 123.txt
Datele sunt scrise, să încercăm să schimbăm permisiunile fișierului:
[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
Văd că datele sunt încă scrise, deși utilizatorul nostru nu are permisiunea de a scrie în fișier. Să încercăm să-l ștergem:
[user@localhost ]$ sudo rm 123.txt
[user@localhost ]$ ls 123.txt
ls: cannot access 123.txt: No such file or directory
Unde sunt scrise datele? Și sunt scrise de fapt? Să verifică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)
Da, descriitorul nostru de fișier există în continuare, iar noi putem lucra cu acest descriitor de fișier ca și cum ar fi fișierul nostru vechi; putem să-l citim, să-l golim și să-l copiem.
Să ne uităm la dimensiunea fișierului:
[user@localhost ]$ lsof | grep 123.txt
python 31083 user 3w REG 8,5 19923457 2621522 /home/user/123.txt
Dimensiunea fișierului este 19923457. Să încercăm să golim fișierul:
[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
După cum vedem, dimensiunea fișierului crește, iar truncarea nu a funcționat. Să ne îndreptăm către documentația apelului de sistem. . Dacă atunci când deschidem fișierul folosim flagul O_APPEND, sistemul de operare verifică dimensiunea fișierului la fiecare scriere și scrie datele la sfârșitul fișierului, făcând acest lucru atomic. Aceasta permite mai multor fire sau procese să scrie în același fișier. Dar în codul nostru nu folosim acest flag. Putem observa o altă dimensiune a fișierului în lsof după truncare, doar dacă deschidem fișierul pentru a adăuga date, așa că în codul nostru, în loc de
with open("123.txt", "w") as f:
trebuie să folosim
with open("123.txt", "a") as f:
Verificăm cu flagul „w”
[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
și cu flag-ul „a”
[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
Programăm un proces deja pornit
Adesea, programatorii folosesc debuggere (de exemplu, GDB) sau diferite niveluri de logare în aplicație atunci când creează și testează programe. Linux oferă posibilitatea de a scrie și modifica efectiv o programă deja pornită, de exemplu, pentru a schimba valorile variabilelor, a stabili un breakpoint etc.
Revenind la întrebarea originală despre lipsa de spațiu pe disc pentru a salva un fișier, să încercăm să simulăm problema.
Să creăm un fișier pentru secțiunea noastră, pe care îl vom monta ca un disc separat:
[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 ~]$
Să creăm un sistem de fișiere:
[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 ~]$
Să montăm sistemul de fișiere:
[user@localhost ~]$ sudo mount ~/tempfile_for_article.dd /mnt/
[sudo] parola pentru user:
[user@localhost ~]$ df -h | grep mnt
/dev/loop0 8.7M 172K 7.9M 3% /mnt
Creăm un director cu proprietarul nostru:
[user@localhost ~]$ sudo mkdir /mnt/logs
[user@localhost ~]$ sudo chown user: /mnt/logs
Vom deschide fișierul doar pentru scriere în programul nostru:
with open("/mnt/logs/123.txt", "w") as f:
Lansăm
[user@localhost ]$ python openforwrite.py
Așteptăm câteva secunde
[user@localhost ~]$ df -h | grep mnt
/dev/loop0 8.7M 8.0M 0 100% /mnt
Așadar, am obținut problema descrisă la începutul acestui articol. Spațiu liber 0, spațiu folosit 100%.
Ne amintim că, conform cerințelor problemei, încercăm să salvăm date foarte importante care nu pot fi pierdute. Și, în același timp, trebuie să reparăm serviciul fără a reporni procesul.
Să zicem că, totuși, avem spațiu pe disc, dar în altă partiție, de exemplu în /home.
Să încercăm să „reprogramăm pe loc” codul nostru.
Să verificăm PID-ul procesului nostru, care a ocupat tot spațiul pe disc:
[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
Ne conectăm la proces prin gdb
[user@localhost ~]$ gdb -p 10078
...
(gdb)
Verificăm descriptorii de fișiere deschise:
(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
Verificăm informațiile despre descriptorul de fișier cu numărul 3, care ne interesează
(gdb) shell cat /proc/10078/fdinfo/3
pos: 8189952
flags: 0100001
mnt_id: 482
Ținând cont de apelul de sistem pe care îl face Python (vedeți mai sus, unde am rulat strace și am găsit apelul open), în timpul procesării codului nostru pentru deschiderea fișierului, facem același lucru, dar în numele procesului nostru, de această dată trebuie să înlocuim biții O_WRONLY|O_CREAT|O_TRUNC cu o valoare numerică. Pentru aceasta, deschidem sursele nucleului, de exemplu, și ne uităm la ce semnale corespund acești biți
#define O_WRONLY 00000001
#define O_CREAT 00000100
#define O_TRUNC 00001000
Unii toate valorile într-una singură, obținem 00001101
Rulăm apelul nostru din gdb
(gdb) call open("/home/user/123.txt", 00001101,0666)
$1 = 4
Așadar, am obținut un nou descriptor de fișier cu numărul 4 și un fișier nou deschis pe o altă partiție, să verifică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
Ne am amintit de exemplul cu pipe – cum bash modifică descriptorii de fișiere, și deja am învățat apelul de sistem dup2.
Încercăm să înlocuim un descriptor de fișier cu altul
(gdb) call dup2(4,3)
$2 = 3
Verifică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
Închidem descriptorul de fișier 4, pentru că nu ne mai trebuie:
(gdb) call close (4)
$1 = 0
Și ieșim din 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
Verificăm noul fișier:
[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
După cum vedem, datele se scriu în noul fișier, să verificăm pe cel vechi:
[user@localhost ~]$ ls -lah /mnt/logs/123.txt
-rw-rw-r-- 1 user user 7.9M Oct 8 11:08 /mnt/logs/123.txt
Datele nu s-au pierdut, aplicația funcționează, logurile sunt scrise în noua locație.
Să complicăm puțin sarcina
Să ne imaginăm că datele sunt importante pentru noi, dar nu avem spațiu pe disc în niciuna dintre partiții și nu putem conecta un disc.
Ce putem face este să redirecționăm undeva datele noastre, de exemplu într-un pipe, iar datele din pipe să fie redirecționate în rețea printr-un program, de exemplu netcat.
Putem crea un pipe denumit folosind comanda mkfifo. Aceasta va crea un pseudo-fișier în sistemul de fișiere, chiar dacă nu există spațiu liber pe el.
Repornim aplicația și verifică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
Nu mai este spațiu pe disc, dar reușim să creăm un pipe numit acolo:
[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
Acum trebuie să învelim cumva toate datele care ajung în acest pipe pe un alt server prin rețea, pentru aceasta se pot folosi tot netcat.
Pe serverul remote-server.example.com pornim
[user@localhost ~]$ nc -l 7777 > 123.txt
Pe serverul nostru problematic, pornim într-un terminal separat
[user@localhost ~]$ nc remote-server.example.com 7777 < /mnt/logs/megapipe
Acum toate datele care ajung în pipe vor ajunge automat pe stdin în netcat, care le va trimite în rețea pe portul 7777.
Tot ce ne rămâne de făcut este să începem să scriem datele noastre în acest pipe numit.
Avem deja o aplicație pornită:
[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
Dintre toate flagurile, avem nevoie doar de O_WRONLY deoarece fișierul deja există și nu trebuie să-l curățăm.
[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
Verificăm serverul remote remote-server.example.com
[user@localhost ~]$ ls -lah 123.txt
-rw-rw-r-- 1 user user 38M Oct 8 14:21 123.txt
Datele sunt în curs de verificare, analizăm serverul problematic
[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
Datele au fost salvate, problema este rezolvată.
Profit de această ocazie pentru a saluta colegii de la Degiro.
Ascultați podcasturile Radio-T.
Toată lumea să aibă parte de bine.
Ca temă pentru acasă, vă propun să vă gândiți ce va conține fișierul descriptorilor procesului cat și sleep dacă rulați această comandă:
[user@localhost ~]$ cat /dev/zero 2>/dev/null| sleep 10000
Sursa: habr.com
