Descriptor de fișier în Linux cu exemple

Odată, în timpul unui interviu, am fost întrebat, ce veți face dacă găsiți un serviciu care nu funcționează din cauza faptului că discul a rămas fără spațiu?

Bineînțeles, i-am răspuns că o să văd ce este ocupat de acest loc și, dacă se poate, o să curăț locul.
Apoi intervievatorul a întrebat, ce se întâmplă dacă nu există spațiu liber pe partiție, dar nici nu vedeți niciun fișier care ar ocupa tot spațiul?

La aceasta am spus că poți oricând să te uiți la descriptori de fișiere deschise, de exemplu cu comanda lsof, și să înțelegi care aplicație a ocupat tot spațiul disponibil, iar apoi poți acționa în funcție de circumstanțe, în funcție de necesitatea datelor. .

Intervievatorul m-a întrerupt la ultimul cuvânt, adăugând la întrebarea lui: „Să presupunem că nu avem nevoie de date, este doar un jurnal de depanare, dar aplicația nu funcționează pentru că nu poate scrie o depanare”?

„Bine”, am răspuns, „putem dezactiva depanarea în configurația aplicației și o putem reporni.”
Intervievatorul a obiectat: „Nu, nu putem reporni aplicația, avem încă date importante stocate în memorie, iar clienții importanți sunt conectați la serviciul în sine, pe care nu îl putem forța să-l reconectam din nou”.

„bine”, am spus, „dacă nu putem reporni aplicația și datele nu sunt importante pentru noi, atunci pur și simplu putem șterge acest fișier deschis prin descriptorul de fișier, chiar dacă nu îl vedem în comanda ls. pe sistemul de fișiere.”

Intervievatorul a fost mulțumit, dar eu nu.

Apoi m-am gândit, de ce persoana care îmi testează cunoștințele nu sapă mai adânc? Dar dacă datele sunt importante până la urmă? Ce se întâmplă dacă nu putem reporni un proces, iar procesul scrie în sistemul de fișiere pe o partiție care nu are spațiu liber? Ce se întâmplă dacă nu putem pierde nu numai datele care au fost deja scrise, ci și datele pe care acest proces le scrie sau încearcă să le scrie?

Pălărie mare

La începutul carierei mele, am încercat să creez o mică aplicație care trebuia să stocheze informații despre utilizator. Și apoi m-am gândit, cum pot potrivi utilizatorul cu datele lui. De exemplu, îl am pe Ivanov Ivan Ivanovici și el are câteva informații, dar cum pot să mă împrietenesc cu ei? Pot să subliniez direct că câinele numit „Tuzik” îi aparține tocmai acestui Ivan. Dar dacă își schimbă numele și în loc de Ivan devine, de exemplu, Olya? Atunci se va dovedi că Olya Ivanovna Ivanova a noastră nu va mai avea un câine, iar Tuzik-ul nostru va aparține în continuare Ivan inexistentului. O bază de date care a oferit fiecărui utilizator un identificator unic (ID) a ajutat la rezolvarea acestei probleme, iar Tuzik-ul meu a fost legat de acest ID, care, de fapt, era doar un număr de serie. Astfel, proprietarul asului avea numărul de identitate 2, iar la un moment dat Ivan a fost sub acest ID, iar apoi Olya a devenit sub același ID. Problema umanității și a creșterii animalelor a fost practic rezolvată.

Descriptor de fișier

Problema fișierului și a programului care funcționează cu acest fișier este aproximativ aceeași cu cea a câinelui și a omului nostru. Să presupunem că am deschis un fișier numit ivan.txt și am început să scriu cuvântul tuzik în el, 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 dosarul rămâne același și tot vreau să-mi înregistrez asul în el. De fiecare dată când un fișier este deschis printr-un apel de sistem deschide în orice limbaj de programare primesc un ID unic care mă indică către un fișier, acest ID este descriptorul fișierului. Și nu contează deloc ce și cine face cu acest fișier în continuare, poate fi șters, poate fi redenumit, proprietarul poate fi schimbat, sau drepturile de citire și scriere pot fi luate, voi avea în continuare acces la el, pentru că la momentul deschiderii fișierului aveam drepturi de a-l citi și/sau de a-l scrie și am reușit să încep să lucrez cu el, ceea ce înseamnă că trebuie să o fac în continuare.

În Linux, biblioteca libc deschide 3 fișiere descriptor pentru fiecare aplicație (proces) care rulează, numerotate 0,1,2. Mai multe informații pot fi găsite pe link-uri om stdio и om stdout

  • Descriptorul de fișier 0 se numește STDIN și este asociat cu introducerea aplicației
  • Descriptorul de fișier 1 se numește STDOUT și este folosit de aplicații pentru a scoate date, cum ar fi comenzile de imprimare
  • Descriptorul de fișier 2 se numește STDERR și este folosit de aplicații pentru a afișa mesaje de eroare.

Dacă în programul dvs. deschideți orice fișier pentru citire sau scriere, atunci cel mai probabil veți obține primul ID gratuit și va fi numărul 3.

Lista descriptorilor de fișiere poate fi vizualizată pentru orice proces dacă îi cunoașteți PID.

De exemplu, să deschidem consola bash și să ne uităm la PID-ul procesului nostru

[user@localhost ]$ echo $$
15771

În a doua consolă să alergăm

[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

Puteți ignora în siguranță descriptorul de fișier numărul 255 în scopul acestui articol; acesta a fost deschis pentru nevoile sale de bash în sine, și nu de biblioteca conectată.

Acum toate cele 3 fișiere descriptor sunt asociate cu dispozitivul pseudo terminal /dev/pts, dar le putem manipula, de exemplu, să le rulăm într-o a doua consolă

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

Și în prima consolă vom vedea

[user@localhost ]$ hello world

Redirecționare și țeavă

Puteți suprascrie cu ușurință aceste 3 fișiere descriptor în orice proces, inclusiv în bash, de exemplu printr-o conductă care conectează două procese, vezi

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

Puteți rula singur această comandă strace -f și vezi ce se întâmplă înăuntru, dar îți voi spune pe scurt.

Procesul nostru părinte bash cu PID 15771 analizează comanda noastră și înțelege exact câte comenzi vrem să rulăm, în cazul nostru există două dintre ele: cat și sleep. Bash știe că trebuie să creeze două procese copil și să le îmbine într-o singură conductă. În total, bash va avea nevoie de 2 procese copil și o conductă.

Bash rulează un apel de sistem înainte de a crea procese copil ţeavă și primește noi descriptori de fișiere pe buffer-ul temporar pipe, dar acest buffer nu conectează încă cele două procese copil.

Pentru procesul părinte, se pare că există deja o conductă, dar încă nu există procese secundare:

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, folosind apelul de sistem clona bash creează două procese copil, iar cele trei procese ale noastre vor arăta astfel:

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

Nu uitați că clonarea clonează procesul împreună cu toți descriptorii de fișiere, astfel încât aceștia vor fi la fel în procesul părinte și în cel copil. Sarcina procesului părinte cu PID 15771 este de a monitoriza procesele copil, așa că așteaptă pur și simplu un răspuns din partea copiilor.

Prin urmare, nu are nevoie de pipe și închide descriptorii de fișier numerotați 3 și 4.

În primul proces de bash copil cu PID 9004, apelul de sistem dup2, schimbă descriptorul nostru de fișier STDOUT numărul 1 într-un descriptor de fișier care indică spre pipe, în cazul nostru este numărul 3. Astfel, tot ceea ce primul proces copil cu PID 9004 scrie în STDOUT va ajunge automat în bufferul pipe.

În al doilea proces copil cu PID 9005, bash folosește dup2 pentru a schimba descriptorul de fișier STDIN numărul 0. Acum tot ceea ce va citi al doilea bash cu PID 9005 va fi citit din conductă.

După aceasta, descriptorii de fișiere numerotați 3 și 4 sunt, de asemenea, închise în procesele copil, deoarece nu mai sunt utilizați.

Ignorez în mod deliberat descriptorul de fișier 255; este folosit în scopuri interne de bash în sine și va fi, de asemenea, închis în procesele copil.

Apoi, în primul proces copil cu PID 9004, bash începe să folosească un apel de sistem Exec fișierul executabil pe care l-am specificat pe linia de comandă, în cazul nostru este /usr/bin/cat.

În al doilea proces copil cu PID 9005, bash rulează al doilea executabil pe care l-am specificat, în cazul nostru /usr/bin/sleep.

Apelul de sistem exec nu închide mânerele fișierelor decât dacă acestea au fost deschise cu indicatorul O_CLOEXEC în momentul în care a fost efectuat apelul deschis. În cazul nostru, după lansarea fișierelor executabile, toți descriptorii de fișiere actuali vor fi salvați.

Verificați î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 vedea, numărul unic al țevii noastre este același în ambele procese. Astfel avem o legătură între două procese diferite cu același părinte.

Pentru cei care nu sunt familiarizați cu apelurile de sistem pe care le folosește bash, recomand să rulați comenzile prin strace și să vedeți ce se întâmplă în interior, de exemplu, astfel:

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

Să revenim la problema noastră cu spațiul redus pe disc și încercarea de a salva date fără a reporni procesul. Să scriem un mic program care va scrie aproximativ 1 megaoctet pe secundă pe disc. Mai mult, dacă din anumite motive nu am reușit să scriem date pe disc, pur și simplu vom ignora acest lucru și vom încerca să scriem din nou datele într-o secundă. În exemplul în care folosesc Python, 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 fișierelor

[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 puteți vedea, avem cei 3 descriptori 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, încercăm să schimbăm permisiunile pe fișier:

[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

Vedem că datele sunt încă în curs de scris, deși utilizatorul nostru nu are permisiunea de a scrie în fișier. Să încercăm să-l eliminăm:

[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 deloc? 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, descriptorul nostru de fișier încă există și putem trata acest descriptor de fișier ca fișierul nostru vechi, îl putem citi, șterge și copia.

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ă ștergem 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 puteți vedea, dimensiunea fișierului crește doar și trunchiul nostru nu a funcționat. Să ne uităm la documentația apelului de sistem deschide. Dacă folosim indicatorul O_APPEND atunci când deschidem un fișier, atunci cu fiecare scriere, sistemul de operare verifică dimensiunea fișierului și scrie date până la sfârșitul fișierului și face acest lucru atomic. Acest lucru permite mai multor fire sau procese să scrie în același fișier. Dar în codul nostru nu folosim acest steag. Putem vedea o dimensiune diferită a fișierului în lsof după trunk numai dacă deschidem fișierul pentru scriere suplimentară, ceea ce înseamnă, în schimb, în ​​codul nostru

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

trebuie să punem

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

Verificarea cu steagul „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 steagul „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

Programarea unui proces care rulează deja

Adesea, programatorii, atunci când creează și testează programe, folosesc depanatoare (de exemplu GDB) sau diferite niveluri de logare în aplicație. Linux oferă posibilitatea de a scrie și schimba efectiv un program care rulează deja, de exemplu, de a schimba valorile variabilelor, de a seta un punct de întrerupere etc., etc.

Revenind la întrebarea inițială despre spațiul pe disc insuficient pentru a scrie un fișier, să încercăm să simulăm problema.

Să creăm un fișier pentru partiția 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 ~]$

Montați sistemul de fișiere:

[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

Creăm un director cu proprietarul nostru:

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

Să deschidem fișierul pentru scriere doar în programul nostru:

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

Lansa

[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

Deci, avem problema descrisă la începutul acestui articol. Spatiu liber 0, 100% ocupat.

Ne amintim că, în funcție de condițiile sarcinii, încercăm să înregistră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ă presupunem că mai avem spațiu pe disc, dar într-o altă partiție, de exemplu în /home.

Să încercăm să „reprogramăm din mers” codul nostru.

Să ne uităm la PID-ul procesului nostru, care a consumat 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

Conectați-vă la proces prin gdb

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

Să ne uităm la descriptorii fișierelor 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

Ne uităm la informațiile despre descriptorul de fișier numărul 3, care ne interesează

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

Ținând cont de ce apel de sistem face Python (vezi mai sus unde am rulat Strace și am găsit apelul deschis), atunci când procesăm codul nostru pentru a deschide un fișier, facem același lucru în numele procesului nostru, dar avem nevoie de O_WRONLY|O_CREAT| Biții O_TRUNC se înlocuiesc cu o valoare numerică. Pentru a face acest lucru, deschideți sursele nucleului, de exemplu aici și uită-te la ce steaguri sunt responsabile pentru ce

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

Combinăm toate valorile într-una singură, obținem 00001101

Ne rulăm apelul de la gdb

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

Deci avem un nou descriptor de fișier cu numărul 4 și un nou fișier deschis pe o altă partiție, 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 amintim exemplul cu pipe - cum bash modifică descriptorii fișierelor și am învățat deja 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, deoarece nu avem nevoie de el:

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

Și ieșiți 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

Verificarea noului 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 puteți vedea, datele sunt scrise într-un fișier nou, să-l 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

Nu se pierd date, aplicația funcționează, jurnalele sunt scrise într-o nouă 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 discul.

Ceea ce putem face este să ne redirecționăm datele undeva, de exemplu către pipe și, la rândul său, să redirecționăm datele de la pipe către rețea printr-un program, de exemplu netcat.
Putem crea o conductă numită cu comanda mkfifo. Va crea un pseudo fișier pe sistemul de fișiere chiar dacă nu există spațiu liber pe el.

Reporniți aplicația și verificați:

[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 există spațiu pe disc, dar creăm cu succes o conductă 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ă împachetăm cumva toate datele care intră în această conductă către un alt server prin intermediul rețelei; același netcat este potrivit pentru asta.

Pe serverul remote-server.example.com lansăm

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

Pe serverul nostru problematic lansăm într-un terminal separat

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

Acum toate datele care ajung în conductă vor merge automat la stdin în netcat, care le va trimite în rețea pe portul 7777.

Tot ce trebuie să facem este să începem să scriem datele noastre în această conductă numită.

Avem deja aplicația care rulează:

[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 steagurile, avem nevoie doar de O_WRONLY, deoarece fișierul există deja și nu trebuie să-l ștergem

[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

Verificarea serverului la distanță remote-server.example.com

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

Vin datele, verificăm serverul cu probleme

[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 sunt salvate, problema este rezolvată.

Profit de această ocazie pentru a saluta colegii mei din Degiro.
Ascultați podcasturi Radio-T.

Bine tuturor.

Ca teme, vă sugerez să vă gândiți la ceea ce va fi în descriptorii fișierului de proces cat și somn dacă rulați următoarea comandă:

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

Sursa: www.habr.com

Adauga un comentariu