Jednou mi na jednom pohovoru řekli, co uděláš, pokud zjistíš, že služba nefunguje kvůli tomu, že na disku došlo místo?
Samozřejmě jsem odpověděl, že se podívám, co zabírá místo, a pokud to bude možné, uvolním je.
Poté se mě tazatel zeptal, co když na oddíle není žádné volné místo, ale také nevidíš žádné soubory, které by všechno místo zabíraly?
Na to jsem řekl, že vždy lze zkontrolovat otevřené popisovače souborů, například příkazem lsof, a zjistit, která aplikace zabírá veškeré dostupné místo, a poté postupovat podle okolností, v závislosti na tom, zda jsou data potřebná.
Tazatel mě přerušil na posledním slově a doplnil svou otázku: „Předpokládejme, že data nejsou důležitá, je to jen debuggovací log, ale aplikace nefunguje, protože nemůže zapsat debug?“
„Dobře,“ odpověděl jsem, „můžeme v konfiguraci aplikace vypnout debug a restartovat ji.“
Tazatel oponoval: „Ne, aplikaci nemůžeme restartovat, v paměti stále máme důležitá data a k samotné službě jsou připojeni důležití klienti, které nemůžeme nutit se znovu připojovat.“
„No dobře,“ řekl jsem, „pokud nemůžeme aplikaci restartovat a data nejsou důležitá, můžeme prostě vyčistit tento otevřený soubor prostřednictvím popisovače souboru, i když ho nevidíme v příkazu ls v souborovém systému.“
Tazatel byl spokojen, ale já ne.
Pak jsem si pomyslel, proč člověk, který prověřuje mé znalosti, nezkoumá hlouběji? Co když jsou data přece jen důležitá? Co když nemůžeme restartovat proces, a zároveň tento proces zapisuje na souborový systém na oddílu, kde není volné místo? Co když nemůžeme ztratit nejen již zapsaná data, ale i ta data, která tento proces zapisuje nebo se je snaží zapsat?
Tuzi
Na začátku své kariéry jsem se pokusil vytvořit malou aplikaci, ve které se měly ukládat informace o uživatelích. Přemýšlel jsem, jak propojit uživatele s jeho údaji. Měl jsem například Ivana Ivanoviče Ivanova, který měl nějaké údaje, ale jak je spojit? Mohl bych například přímo uvést, že pes jménem „Tuza“ patří tomuto Ivanovi. Ale co když změní jméno a místo Ivana se stane třeba Olí? Pak by naša Olya Ivanovna Ivanova už neměla psa, ale náš Tuza by stále patřil neexistujícímu Ivanovi. Tuto problematiku vyřešila databáze, která každému uživateli přiřazovala unikátní identifikátor (ID), a můj Tuza byl připojen k tomuto ID, které bylo v podstatě jen pořadovým číslem. Takto měl majitel tazika ID číslo 2, a v určitém čase byl tímto ID Ivan, a pak se tímto ID stala Olya. Problém lidstva a chovatelství zvířat byl prakticky vyřešen.
File descriptor
Problém souboru a programu, který s ním pracuje, je zhruba stejný jako u našeho psa a člověka. Představme si, že jsem otevřel soubor s názvem ivan.txt a začal do něj psát slovo tuzik, ale stihl jsem zapsat jen první písmeno „t“, když byl tento soubor někým přejmenován, například na olya.txt. Ale soubor zůstal tím stejným, a já stále chci do něj zapsat svého tazika. Při každém otevření souboru systémovým voláním v jakémkoliv programovacím jazyce dostávám unikátní ID, které mi ukazuje na soubor, toto ID je file descriptor. A vůbec nezáleží na tom, co a kdo dělá s tímto souborem dále, může být smazán, přejmenován, může mu být změněn vlastník nebo může být odebrán práva na čtení a zápis, já budu i nadále mít k němu přístup, protože v okamžiku otevření souboru jsem měl práva pro jeho čtení a/nebo zápis a stihl jsem začít s ním pracovat, což znamená, že bych měl pokračovat v tomto proces.
V Linuxu knihovna libc otevírá pro každou spuštěnou aplikaci (proces) 3 souborové deskriptory, s čísly 0, 1, 2. Více informací můžete najít v odkazech a
- Souborový descriptor 0 se nazývá STDIN a je spojen s vstupem dat pro aplikaci.
- Popis souboru 1 se nazývá STDOUT a používá se aplikacemi pro výstup dat, například příkazy print.
- Popis souboru 2 se nazývá STDERR a používá se aplikacemi pro výstup dat, které informují o chybách.
Pokud ve vaší aplikaci otevřete nějaký soubor pro čtení nebo zápis, pravděpodobně dostanete první volné ID a to bude číslo 3.
Seznam popisů souborů můžete vidět u jakéhokoli procesu, pokud znáte jeho PID.
Například otevřeme konzoli s bash a podíváme se na PID našeho procesu.
[user@localhost ]$ echo $$
15771
V druhé konzoli 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
Popis souboru s číslem 255 můžete v rámci tohoto článku zcela ignorovat, byl otevřen pro potřeby bash, a ne připojenou knihovnou.
Teď jsou všechny 3 popisy souborů spojeny s zařízením pseudoterminálu. , ale stále s nimi můžeme manipulovat, například spustíme ve druhé konzoli
[user@localhost ]$ echo "hello world" > /proc/15771/fd/0
A v první konzoli uvidíme
[user@localhost ]$ hello world
Přesměrování a potrubí
Můžete snadno předefinovat tyto 3 popisy souborů v jakémkoli procesu, včetně bash, například přes potrubí (pipe), které spojuje dva procesy, podíváme se na to.
[user@localhost ]$ cat /dev/zero | sleep 10000
Můžete si tuto příkaz spustit sami a uvidíte, co se uvnitř děje, ale já to stručně shrnu. strace -f Náš rodičovský proces bash s PID 15771 analyzuje náš příkaz a chápe, kolik příkazů chceme spustit, v našem případě jich je dvě: cat a sleep. Bash ví, že musí vytvořit dva dceřiné procesy a spojit je jedním potrubím. Celkem bash potřebuje 2 dceřiné procesy a jedno potrubí.
Před vytvořením dceřiných procesů bash spouští systémový volání
a získává nové popisy souborů pro dočasný buffer potrubí, ale tento buffer dosud nespojuje naše dva dceřiné procesy. Pro rodičovský proces to vypadá tak, jako by potrubí už existovalo, ale dceřiné procesy ještě nejsou:
PID pří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
PID pří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
Poté pomocí systémového volání bash vytvoří dva podřízené procesy a naše tři procesy budou vypadat takto:
PID příkaz
15771 bash
lrwx------ 1 user user 64 7. října 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 7. října 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 7. října 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 7. října 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 7. října 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 7. října 15:42 255 -> /dev/pts/21
PID příkaz
9004 bash
lrwx------ 1 user user 64 7. října 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 7. října 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 7. října 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 7. října 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 7. října 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 7. října 15:57 255 -> /dev/pts/21
PID příkaz
9005 bash
lrwx------ 1 user user 64 7. října 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 7. října 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 7. října 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 7. října 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 7. října 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 7. října 15:57 255 -> /dev/pts/21
Nezapomínejme, že clone klonuje proces spolu se všemi popisovači souborů, takže v rodičovském procesu a v podřízených budou stejné. Úkolem rodičovského procesu s PID 15771 je sledovat podřízené procesy, takže jednoduše čeká na odpověď od podřízených.
Proto pipe není potřeba, a uzavírá popisovače souborů s čísly 3 a 4.
V prvním podřízeném procesu bash s PID 9004, systémovým voláním , měníme náš popisovač souboru STDOUT s číslem 1 na popisovač souboru ukazující na pipe, v našem případě je to číslo 3. Takže vše, co první podřízený proces s PID 9004 zapíše do STDOUT, se automaticky dostane do bufferu pipe.
Ve druhém podřízeném procesu s PID 9005 bash mění pomocí dup2 popisovač souboru STDIN s číslem 0. Nyní všechno, co náš druhý bash s PID 9005 přečte, bude číst z pipe.
Po tomto jsou v podřízených procesech také uzavřeny popisovače souborů s čísly 3 a 4, protože již nejsou používány.
Popisovač souboru 255 ignoruji záměrně, používá se pro interní potřeby samotného bash a v podřízených procesech bude také uzavřen.
Dále v prvním podřízeném procesu s PID 9004 bash spouští pomocí systémového volání spustitelný soubor, který jsme uvedli v příkazovém řádku, v našem případě je to /usr/bin/cat.
Ve druhém podřízeném procesu s PID 9005 bash spouští druhý spustitelný soubor, který jsme uvedli, v našem případě je to /usr/bin/sleep.
Systémové volání exec nezavírá deskriptory souborů, pokud nebyly otevřeny s příznakem O_CLOEXEC při 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 potrubí se v obou procesech shoduje. Tím 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é bash využívá, doporučuji spustit příkazy pomocí strace a podívat se, co se uvnitř děje, 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 pokusem o uložení dat bez restartování 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 moci zapsat data na disk, budeme to prostě ignorovat a zkusíme zapsat data znovu 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
Jak vidíme, máme naše 3 standardní popisovače souborů a ještě jeden, který jsme otevřeli. Zkontrolujeme velikost souboru:
[user@localhost ]$ ls -lah 123.txt
-rw-rw-r-- 1 user user 117M Oct 7 16:30 123.txt
Data se zapisují, zkusíme změnit práva na soubor:
[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 data se stále zapisují, i když náš uživatel nemá právo zapisovat do souboru. Zkusíme ho smazat:
[user@localhost ]$ sudo rm 123.txt
[user@localhost ]$ ls 123.txt
ls: cannot access 123.txt: No such file or directory
Kam se zapisují data? A píší se vůbec? Kontrolujeme:
[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)
Ano, náš popisovač souboru stále existuje a můžeme s ním pracovat jako s naším starým souborem, můžeme ho číst, čistit a kopírovat.
Podívejme se na velikost souboru:
[user@localhost ]$ lsof | grep 123.txt
python 31083 user 3w REG 8,5 19923457 2621522 /home/user/123.txt
Velikost souboru je 19923457. Zkusíme soubor vyčistit:
[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
Jak vidíme, velikost souboru se pouze zvyšuje a náš truncate nefungoval. Odkážeme na dokumentaci k systémovému volání . Pokud při otevírání souboru použijeme příznak O_APPEND, operační systém při každém zápisu kontroluje velikost souboru a zapisuje data na konec souboru, přičemž to dělá atomicky. To umožňuje několika vláknům nebo procesům zapisovat do stejného souboru. Ale v našem kódu tuto příznak nepoužíváme. Můžeme vidět jinou velikost souboru v lsof po truncate pouze pokud otevřeme soubor pro přidání, a to znamená, že v našem kódu místo
with open("123.txt", "w") as f:
musíme napsat
with open("123.txt", "a") as f:
Kontrolujeme s příznakem „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 flagem
[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 již spuštěný proces
Programátoři často při vytváření a testování programů používají debuggery (například GDB) nebo různé úrovně logování v aplikaci. Linux umožňuje prakticky psát a měnit již spuštěný program, například měnit hodnoty proměnných, nastavovat breakpointy atd.
Vraťme se k původní otázce o nedostatku místa na disku pro zápis souboru, pokusíme se simulovat problém.
Vytvoříme soubor pro naši partition, který připojíme jako samostatný disk:
[user@localhost ~]$ dd if=/dev/zero of=~/tempfile_for_article.dd bs=1M count=10
10+0 záznamů
10+0 záznamů výstupu
10485760 bajtů (10 MB) zkopírováno, 0.00525929 s, 2.0 GB/s
[user@localhost ~]$
Vytvoříme souborový systém:
[user@localhost ~]$ mkfs.ext4 ~/tempfile_for_article.dd
mke2fs 1.42.9 (28-Dec-2013)
/home/user/tempfile_for_article.dd není speciální blokové zařízení.
Pokračovat i tak? (y,n) y
...
Zápis superbloků a účetních informací o souborovém systému: hotovo
[user@localhost ~]$
Připojíme souborový systém:
[user@localhost ~]$ sudo mount ~/tempfile_for_article.dd /mnt/
[sudo] heslo pro uživatele:
[user@localhost ~]$ df -h | grep mnt
/dev/loop0 8.7M 172K 7.9M 3% /mnt
Vytvoříme adresář s naším vlastníkem:
[user@localhost ~]$ sudo mkdir /mnt/logs
[user@localhost ~]$ sudo chown user: /mnt/logs
Otevřeme soubor pouze pro zápis v našem programu:
with open("/mnt/logs/123.txt", "w") as f:
Spouštíme
[user@localhost ]$ python openforwrite.py
Čekáme několik sekund
[user@localhost ~]$ df -h | grep mnt
/dev/loop0 8.7M 8.0M 0 100% /mnt
A máme tedy problém popsaný na začátku tohoto článku. Volného místa 0, obsazeného 100%.
Pamatujeme si, že podle podmínek úkolu se snažíme zapsat velmi důležitá data, která nelze ztratit. A přitom musíme opravit službu bez restartování procesu.
Předpokládejme, že máme místo na disku, ale na jiné partition, například v /home.
Pokud zkusíme "přeprogramovat za běhu" náš kód.
Podíváme se na PID našeho procesu, který zabral celé místo 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
Připojíme se k procesu přes gdb
[user@localhost ~]$ gdb -p 10078
...
(gdb)
Podíváme se na otevřené deskriptory souborů:
(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
Podíváme se na informace o souborovém deskriptoru číslo 3, který nás zajímá
(gdb) shell cat /proc/10078/fdinfo/3
pos: 8189952
flags: 0100001
mnt_id: 482
S ohledem na systémové volání, které Python provádí (viz výše, kde jsme spustili strace a zjistili volání open), když zpracovává náš kód pro otevření souboru, děláme to stejné sami jménem našeho procesu, ale bity O_WRONLY|O_CREAT|O_TRUNC musíme nahradit číselnou hodnotou. K tomu otevřeme zdrojové kódy jádra, například a podíváme se, jaké flagy za co odpovídají.
#define O_WRONLY 00000001
#define O_CREAT 00000100
#define O_TRUNC 00001000
Spojíme všechny hodnoty do jednoho, dostaneme 00001101.
Spustíme naše volání z gdb.
(gdb) call open("/home/user/123.txt", 00001101,0666)
$1 = 4
Takže jsme získali nový deskriptor souboru s číslem 4 a nový otevřený soubor na jiném oddílu, prověříme:
(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
Pamatujeme si příklad s pipe — jak bash mění deskriptory souborů, a už jsme se naučili systémové volání dup2.
Zkusíme nahradit jeden deskriptor souboru jiný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
Zavřeme deskriptor souboru 4, protože ho nepotřebujeme:
(gdb) call close (4)
$1 = 0
A opustíme 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
Prověřujeme nový soubor:
[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
Jak vidíme, data se zapisují do nového souboru, prověřujeme 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
Data nebyla ztracena, aplikace funguje, logy se zapisují na nové místo.
Trochu ztížíme úkol.
Představme si, že jsou pro nás data důležitá, ale na disku nemáme místo v žádném z oddílů a nemůžeme připojit disk.
Co můžeme udělat, je přesměrovat naše data někam jinam, například do pipe, a data z pipe zase přesměrovat do sítě přes nějaký program, například netcat.
Můžeme vytvořit pojmenovaný pipe příkazem mkfifo. Vytvoří pseudo-soubor v souborovém systému, i když na něm není volné místo.
Restartujeme aplikaci 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 není místo, ale úspěšně tam vytváříme pojmenovanou trubku:
[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
Nyní potřebujeme nějak zabalit všechna data, která se dostanou do této trubky na jiném serveru přes síť, k tomu se hodí ten samý netcat.
Na serveru remote-server.example.com spouštíme
[user@localhost ~]$ nc -l 7777 > 123.txt
Na našem problémovém serveru spouštíme v jiném terminálu
[user@localhost ~]$ nc remote-server.example.com 7777 < /mnt/logs/megapipe
Nyní všechna data, která se dostanou do trubky, automaticky přejdou na stdin do netcat, který je pošle do sítě na port 7777.
Vše, co nám zbývá udělat, je začít zapisovat naše data do této pojmenované trubky.
Již máme spuštěnou aplikaci:
[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šech příznaků potřebujeme pouze O_WRONLY, protože soubor již existuje a není třeba ho čistit.
[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 proudí, kontrolujeme problémový 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 pozdravuji kolegy z firmy Degiro.
Poslouchejte podcasty Rádia-T.
Všem přejeme vše dobré.
Jako domácí úkol navrhuji zamyslet se nad tím, co bude v souborových deskriptorech procesů cat a sleep, pokud spustíme tento příkaz:
[user@localhost ~]$ cat /dev/zero 2>/dev/null| sleep 10000
Zdroj: habr.com
