Súborový descriptor v Linuxe s príkladmi

Raz sa ma na jednej interview spýtali, čo by som robil, keby som zistil, že služba nefunguje kvôli tomu, že na disku už nezostalo miesto?

Samozrejme, odpovedal som, že sa pozriem, čím je toto miesto obsadené a ak je to možné, tak uvoľním miesto.
Na to sa interviewér spýtal, čo ak na oddiele nie je voľné miesto, ale ani súbory, ktoré by zaberali celé miesto, tiež nevidím?

Na to som povedal, že vždy sa dajú pozrieť otvorené súborové deskriptory, napríklad príkazom lsof a zistiť, ktorá aplikácia zabrala všetko dostupné miesto, a potom môžeme konať podľa okolností, v závislosti od toho, či sú dáta potrebné.

Interviewér ma prerušil na poslednom slove, doplniac svoju otázku: „Predpokladajme, že dáta nie sú dôležité, je to len debuggovací log, ale aplikácia nefunguje, pretože nemôže zapísať debug?“

„OK,“ odpovedal som, „môžeme vypnúť debuggovanie v konfigurácii aplikácie a reštartovať ju.“
Interviewér namietol: „Nie, aplikáciu nemôžeme reštartovať, máme v pamäti stále dôležité dáta a k samotnej službe sú pripojení dôležití klienti, ktorých nemôžeme nútiť znovu sa pripojiť.“

„Nuž dobre,“ povedal som, „ak nemôžeme reštartovať aplikáciu a dáta sú nám nepodstatné, môžeme jednoducho vyčistiť tento otvorený súbor cez súborový deskriptor, aj keď ho nevidíme v príkaze ls v súborovom systéme.“

Interviewér bol spokojný, ale ja nie.

Potom som si pomyslel, prečo človek, ktorý kontroluje moje vedomosti, nekopá hlbšie? A čo ak sú dáta predsa dôležité? Čo ak nemôžeme reštartovať proces a ten proces zapisuje do súborového systému na oddiel, na ktorom už nie je voľné miesto? Čo ak nemôžeme stratiť nielen už zapísané dáta, ale ani dáta, ktoré tento proces píše alebo sa snaží zapísať?

Tuzik

Na začiatku mojej kariéry som sa snažil vytvoriť malú aplikáciu, v ktorej bolo potrebné uchovávať informácie o používateľoch. A vtedy som si hovoril, ako priradiť používateľa k jeho údajom. Mám napríklad Ivanova Ivana Ivanoviča, a má nejaké údaje, ale ako ich spojiť? Môžem priamo uviesť, že pes menom „Tuzik“ patrí tomuto Ivanovi. Ale čo ak zmení meno a namiesto Ivana sa stane napríklad Oľou? Potom zistí, že naša Oľga Ivanovna Ivanová už nebude mať psa, a náš Tuzik stále patrí neexistujúcemu Ivanovi. Riešenie tejto problémovej situácie poskytla databáza, ktorá každému používateľovi pridelila jedinečný identifikátor (ID), a môj Tuzik sa viazal na tento ID, ktorý bol v podstate len poradovým číslom. Takže majiteľ Tuzika mal ID s číslom 2, a na nejaký čas bol pod týmto ID Ivan, a potom sa pod týmto istým ID stala Oľa. Problém ľudstva a chovateľstva bol prakticky vyriešený.

Popisovač súboru

Problém súboru a programu, ktorý pracuje s týmto súborom, je približne rovnaký ako náš pes a človek. Predpokladajme, že som otvoril súbor s názvom ivan.txt a začal do neho zapisovať slovo tuzik, ale stihol som zapísať iba prvé písmeno „t“ do súboru, a tento súbor bol niekým premenovaný, povedzme na olya.txt. Ale súbor zostal tým istým, a ja stále chcem zapísať do neho svojho Tuzika. Každý raz pri otvorení súboru systémovým volaním open v akomkoľvek programovacom jazyku dostávam jedinečný ID, ktorý mi ukazuje na súbor, tento ID je popisovač súboru. A vôbec nezáleží na tom, čo a kto robí s týmto súborom ďalej, môže byť odstránený, môže byť premenovaný, môže byť zmenený vlastník alebo môžu byť odobrané práva na čítanie a zápis, stále budem mať k nemu prístup, pretože v čase otvorenia súboru som mal práva na jeho čítanie a/alebo zápis a stihol som s ním začať robiť, a teda by som mal pokračovať.

V Linuxe knižnica libc otvára pre každú spustenú aplikáciu (proces) 3 popisovače súborov, s číslami 0, 1, 2. Viac informácií nájdete na odkazy man stdio a man stdout

  • Popisovač súboru 0 sa nazýva STDIN a je spojený so vstupom dát do aplikácie
  • Súborový deskriptor 1 sa nazýva STDOUT a používajú ho aplikácie na výstup údajov, napríklad príkazy print.
  • Súborový deskriptor 2 sa nazýva STDERR a používajú ho aplikácie na výstup údajov, ktoré informujú o chybách.

Ak vo vašom programe otvoríte akýkoľvek súbor na čítanie alebo zápis, pravdepodobne dostanete prvé voľné ID a to bude číslo 3.

Zoznam súborových deskriptorov môžete vidieť pri akomkoľvek procese, ak poznáte jeho PID.

Napríklad otvoríme konzolu s bash a pozrieme sa na PID nášho procesu.

[user@localhost ]$ echo $$
15771

V druhej konzole spustíme.

[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

Súborový deskriptor s číslom 255 môžete pokojne ignorovať v rámci tohto článku, bol otvorený pre svoje potreby už samotným bash, a nie prelinkovanou knihovňou.

V súčasnosti sú všetky 3 súborové deskriptory prepojené s pseudotermínovým zariadením. /dev/pts, ale aj tak s nimi môžeme manipulovať, napríklad spustíme v druhej konzole.

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

A v prvej konzole uvidíme.

[user@localhost ]$ hello world

Prevod a rúrka.

Tieto 3 súborové deskriptory môžete ľahko prepísať v akomkoľvek procese, vrátane bash, napríklad cez rúrku, ktorá prepája dva procesy, pozrime sa.

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

Môžete si túto príkaz spustiť s. strace -f a vidieť, čo sa deje vo vnútri, ale v skratke vám to prerozprávam.

Náš rodičovský proces bash s PID 15771 spracováva náš príkaz a vie, koľko príkazov chceme spustiť, v našom prípade sú to dva: cat a sleep. Bash vie, že musí vytvoriť dva potomkovské procesy a spojiť ich jednou rúrkou. Takže bash potrebuje 2 potomkovské procesy a jednu rúrku.

Pred vytvorením potomkovských procesov bash spúšťa systémový hovor. pipe a získa nové súborové deskriptory na dočasný buffer rúrky, ale tento buffer zatiaľ nijako nespojuje naše dva potomkovské procesy.

Pre rodičovský proces to vyzerá tak, akoby rúrka už existovala, ale potomkovské procesy ešte nie sú:

PID    príkaz
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

Potom pomocou systémového volania klon bash vytvára dva podprocesy a naše tri procesy budú vyzerať takto:

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

Nezabúdajme, že clone klonuje proces spolu so všetkými deskriptorami súborov, preto v rodičovskom procese a v podprocesoch budú rovnaké. Úlohou rodičovského procesu s PID 15771 je sledovať podprocesy, preto len čaká na odpoveď od podprocesov.

Preto nie je potrebná rúrka a zatvára deskriptory súborov s číslami 3 a 4.

V prvom podprocese bash s PID 9004, pomocou systémového volania dup2, mení náš deskriptor súboru STDOUT s číslom 1 na deskriptor súboru ukazujúci na rúrku, v našom prípade je to číslo 3. Týmto spôsobom všetko, čo prvý podproces s PID 9004 napíše do STDOUT, sa automaticky dostane do vyrovnávacej pamäte rúrky.

V druhom podprocese s PID 9005 bash mení pomocou dup2 deskriptor súboru STDIN s číslom 0. Teraz všetko, čo náš druhý bash s PID 9005 prečíta, bude čítať z rúrky.

Potom sa aj v podprocesoch zatvárajú deskriptory súborov s číslami 3 a 4, pretože sa už viac nepoužívajú.

Deskriptor súboru 255 zámery ignorujem, používa ho na interné potreby samotný bash a v podprocesoch bude tiež zatvorený.

Ďalej v prvom podprocese s PID 9004 bash spúšťa pomocou systémového volania exec spustiteľný súbor, ktorý sme uviedli v príkazovom riadku, v našom prípade to je /usr/bin/cat.

V druhom podprocese s PID 9005 bash spúšťa druhý spustiteľný súbor, ktorý sme uviedli, v našom prípade to je /usr/bin/sleep.

Systémový volání exec nezavírá deskriptory souborů, pokud nebyly otevřeny s flagem O_CLOEXEC během vykonávání volání open. V našem případě po spuštění spustitelných souborů všechny aktuální deskriptory souborů zůstanou zachovány.

Ověřujeme v konzoli:

[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

Jak vidíte, unikátní číslo našeho pipe máme v obou procesech shodné. Takto máme spojení mezi dvěma různými procesy s jedním rodičem.

Pro ty, kteří nejsou obeznámeni se systémovými voláními, které používá bash, doporučuji spustit příkazy přes strace a podívat se, co se děje uvnitř, například takto:

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

Vraťme se k našemu problému s nedostatkem místa na disku a pokusu o uložení dat bez restartu procesu. Napíšeme malý program, který bude zapisovat na disk přibližně 1 megabajt za sekundu. Pokud z nějakého důvodu nebudeme schopni zaznamenat data na disk, jednoduše to ignorujeme a zkusíme znovu zapisovat za sekundu. V příkladu používám Python, můžete použít jakýkoli jiný programovací jazyk.

[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

Spustíme program a podíváme se na deskriptory souborů.

[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

Ako vidíme, máme naše 3 štandardné deskriptory súborov a ešte jeden, ktorý sme otvorili. Skontrolujeme veľkosť súboru:

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

Dáta sa zapisujú, pokúsime sa zmeniť práva na súbor:

[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

Vidíme, že údaje sa stále zapisujú, aj keď náš používateľ nemá právo zapisovať do súboru. Pokúsime sa ho odstrániť:

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

Kde sa zapisujú údaje? A zapisujú sa vôbec? Skontrolujeme:

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

Áno, náš deskriptor súboru stále existuje a môžeme s ním manipulovať ako s naším starým súborom, môžeme ho čítať, mazať a kopírovať.

Pozrieme sa na veľkosť súboru:

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

Veľkosť súboru je 19923457. Pokúsime sa vyčistiť súbor:

[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

Ako vidíme, veľkosť súboru sa len zvyšuje a náš trunchovanie nefungovalo. Obrátime sa na dokumentáciu k systémovému volaniu. openAk pri otvorení súboru použijeme flag O_APPEND, tak pri každom zápise operačný systém kontroluje veľkosť súboru a zapisuje údaje na koniec súboru, a robí to atomicky. To umožňuje viacerým vláknam alebo procesom zapisovať do rovnakého súboru. Ale v našom kóde tento flag nepoužívame. Môžeme vidieť inú veľkosť súboru v lsof po trunchovaní len ak otvoríme súbor na zápis, a to znamená, že v našom kóde namiesto

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

musíme nastaviť

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

Kontrolujeme s flagom „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

a s príznakom

[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

Programujeme už spustený proces

Programátori často používajú debuggery (napríklad GDB) alebo rôzne úrovne logovania pri vytváraní a testovaní aplikácií. Linux poskytuje možnosť reálne písať a meniť už bežiace programy, napríklad meniť hodnoty premenných, nastavovať zarážky atď.

Vrátime sa k pôvodnej otázke nedostatku miesta na disku pre zápis súboru a pokúsime sa simulovať problém.

Vytvoríme súbor pre našu partíciu, ktorý pripojíme ako samostatný 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 ~]$

Vytvoríme súborový systém:

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

Pripojíme súborový systém:

[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

Vytvoríme adresár s naším vlastníkom:

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

Otvoríme súbor iba na zápis v našom programe:

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

Spúšťame

[user@localhost ]$ python openforwrite.py 

Počkajte niekoľko sekúnd

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

Takže sme narazili na problém popísaný na začiatku tohto článku. Voľného miesta 0, obsadeného 100%.

Pamätáme si, že podľa podmienok úlohy sa snažíme zapísať veľmi dôležité údaje, ktoré sa nesmú stratiť. A pritom musíme opraviť službu bez reštartu procesu.

Predpokladajme, že máme miesto na disku, ale v inej partícii, napríklad v /home.

Pokúsime sa "preprogramovať za chodu" náš kód.

Pozrieme sa na PID nášho procesu, ktorý zaplnil celé miesto na disku:

[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

Pripojíme sa k procesu cez gdb

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

Pozrieme sa na otvorené deskriptory súborov:

(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

Pozrieme sa na informácie o deskriptore súboru s číslom 3, ktorý nás zaujíma

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

Pamätajúc si, ktorý systémový volanie vykonáva Python (pozri vyššie, kde sme spustili strace a našli volanie open), pri spracovaní nášho kódu na otvorenie súboru robíme to isté sami v mene nášho procesu, ale bity O_WRONLY|O_CREAT|O_TRUNC musíme nahradiť číselnou hodnotou. Na to otvoríme zdrojové kódy jadra, napríklad tu a pozrieme sa, aké príznaky za čo zodpovedajú

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

Spojíme všetky hodnoty do jedného, získame 00001101

Spúšťame náš volanie z gdb

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

Takže sme získali nový súborový deskriptor s číslom 4 a nový otvorený súbor na inom oddiele, kontrolujeme:

(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

Pamätáme si príklad s pipe — ako bash mení súborové deskriptory, a už sme sa naučili systémové volanie dup2.

Skúšame nahradiť jeden súborový deskriptor iným

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

Kontrolujeme:

(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

Zatvárame súborový deskriptor 4, pretože ho nepotrebujeme:

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

A vychádzame z 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

Kontrolujeme nový súbor:

[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

Ako vidíme, údaje sú zapisované do nového súboru, kontrolujeme starý:

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

Údaje neboli stratené, aplikácia funguje, logy sa zapisujú na nové miesto.

Trochu zťažime úlohu

Predstavme si, že údaje sú pre nás dôležité, ale nemáme miesto na disku v žiadnom z oddielov a nemôžeme pripojiť disk.

Čo môžeme urobiť, je presmerovať niekam naše údaje, napríklad do pipe, a dáta z pipe naopak presmerovať do siete pomocou nejakej aplikácie, napríklad netcat.
Môžeme vytvoriť pomenovaný pipe príkazom mkfifo. Vytvorí pseudo-súbor na súborovom systéme, aj keď na ňom nie je voľné miesto.

Reštartujeme aplikáciu a kontrolujeme:

[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

Na disku nie je žiadne miesto, ale úspešne tam vytvárame pomenovaný pipe:

[user@localhost ~]$ mkfifo /mnt/logs/megapipe
[user@localhost ~]$ ls -lah /mnt/logs/megapipe 
prw-rw-r-- 1 user user 0 Oct  8 11:28 /mnt/logs/megapipe

Teraz potrebujeme nejako zabalit všetky dáta, ktoré sa dostanú do tohto pipe na iný server cez sieť, k tomu nám poslúži rovnaký netcat.

Na serveri remote-server.example.com spúšťame

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

Na našom problémovom serveri spúšťame v samostatnom termináli

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

Teraz všetky dáta, ktoré sa dostanú do pipe, automaticky pôjdu do stdin v netcat, ktorý ich odošle do siete na port 7777.

Všetko, čo nám ostáva, je začať zapisovať naše dáta do tohto pomenovaného pipe.

Už máme spustenú aplikáciu:

[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

Z všetkých flagov potrebujeme len O_WRONLY, pretože súbor už existuje a nemáme ho potrebu vymazať.

[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

Kontrolujeme vzdálený server remote-server.example.com

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

Data přicházejí, kontrolujeme problematický 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

Data byla uložena, problém vyřešen.

Při této příležitosti zdravím kolegy z firmy Degiro.
Poslouchejte podcasty Rádia-T.

Všem přeji vše dobré.

Jako domácí úkol navrhuji zamyslet se, co bude v souboru deskriptorů procesů cat a sleep, pokud spustíte tento příkaz:

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

Zdroj: habr.com

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster