Fájl leíró Linuxon példákkal

Egyszer, egy interjú során megkérdezték tőlem, mit tennék, ha felfedezném, hogy egy szolgáltatás nem működik, mert a lemezen elfogyott a hely.

Természetesen azt válaszoltam, hogy megnézem, mivel van tele ez a hely, és amennyiben lehetséges, megtisztítom.
Akkor az interjúztató megkérdezte, mi van, ha a partíción nincs szabad hely, de nem látom azt sem, hogy bármi elfoglalná az egész helyet?

Erre azt mondtam, hogy mindig megnézhetjük az nyitott fájl leírókat, például az lsof parancs segítségével, és megtudhatjuk, melyik alkalmazás foglalta el az összes elérhető helyet, aztán a körülményeknek megfelelően cselekedhetünk, attól függően, hogy szükség van-e az adatokra.

Az interjúztató a legutolsó szónál megszakított, kiegészítve a kérdését: „Tegyük fel, hogy az adatokra nincs szükségünk, ez csak egy debuggolási napló, de az alkalmazás nem működik, mert nem tudja írni a debuggolást”?

„Rendben,” válaszoltam, „ki tudjuk kapcsolni a debuggot az alkalmazás konfigurációjában, és újra tudjuk indítani.”
Az interjúztató ellenkezett: „Nem, az alkalmazást nem tudjuk újraindítani, mert a memóriában fontos adatok vannak, és a szolgáltatáshoz fontos ügyfelek csatlakoznak, akiket nem tudunk újra kapcsolódni kényszeríteni.”

„Hát jó,” mondtam, „ha nem tudjuk újraindítani az alkalmazást, és az adatok nem fontosak, akkor egyszerűen kitisztíthatjuk ezt az nyitott fájlt a fájl leírón keresztül, még akkor is, ha az ls parancs segítségével nem látjuk a fájlrendszeren.”

Az interjúztató elégedett volt, de én nem.

Akkor gondoltam, miért nem mélyebbre ás az a személy, aki ellenőrzi a tudásomat? Mi van, ha mégis fontosak az adatok? Mi van, ha nem tudjuk újraindítani a folyamatot, és ez a folyamat ír a fájlrendszerre olyan partíción, ahol nincs szabad hely? Mi van, ha nem tudjuk elveszíteni nemcsak a már rögzített adatokat, hanem azokat az adatokat is, amelyeket ez a folyamat ír vagy próbál írni?

Tuzik

Karrierem elején próbáltam létrehozni egy kis alkalmazást, amely a felhasználók információit tárolta. Akkor gondolkodtam el azon, hogyan tudnám összekapcsolni a felhasználót az adataival. Például, ha van egy Ivanov Ivan Ivanyics, akinek vannak bizonyos adatai, de hogyan tudom ezeket összepárosítani? Közvetlenül megadhatom, hogy a „Tuzik” nevű kutya tulajdonosa ez az Ivan. De mi van, ha Ivan nevet vált, és Olya lesz belőle? Akkor Olya Ivanovna Ivanova már nem lesz kutyatulajdonos, míg Tuzik továbbra is a nem létező Ivan tulajdonában marad. E problémára a megoldást egy adatbázis nyújtotta, amely minden felhasználónak egyedi azonosítót (ID-t) adott, és Tuzik ehhez az ID-hez kapcsolódott, amely lényegében csak egy sorszám volt. Így Tuzik gazdája a 2-es számú ID-vel rendelkezett, és egy adott időpontban ez az ID Ivánhoz tartozott, majd később Olyához. Az emberiség és az állattenyésztés problémája szinte megoldódott.

Fájl leíró

A fájl és az azt kezelő program problémája hasonló a kutyánk és az ember esetéhez. Tegyük fel, hogy megnyitok egy ivan.txt nevű fájlt, és elkezdek bele írni a tuzik szót, de csak az „t” első betűt tudom a fájlba írni, és ezt követően valaki átnevezte, például olya.txt-re. De a fájl továbbra is ugyanaz marad, és én még mindig írni akarok a saját tzuzikámról. Minden alkalommal, amikor egy fájlt megnyitok egy rendszerhívással. open bármely programozási nyelven egy egyedi ID-t kapok, amely a fájlra mutat, és ez az ID a fájl leíró. Teljesen lényegtelen, hogy mi történik a fájllal ezután, törölhetik, átnevezhetik, megváltoztathatják a tulajdonosát, vagy elvehetik a hozzáférést az olvasáshoz és íráshoz, én még mindig hozzáférhetek, mert a fájl megnyitásakor jogom volt olvasni és/vagy írni, és elkezdtem vele dolgozni, ezért folytatnom kell ezt a munkát.

Linux alatt a libc könyvtár minden futó alkalmazás (folyamat) számára 3 fájl leírót nyit meg, a 0, 1, 2 számokkal. További információt találhat a következő linkeken: man stdio és man stdout

  • A 0-ás fájl leíró STDIN néven ismert, és az alkalmazás adatbeviteli asociációjaként szolgál.
  • Az 1-es fájl leíró neve STDOUT, amelyet a programok adatkiadáshoz használnak, például a print parancsokhoz.
  • A 2-es fájl leíró neve STDERR, amelyet a programok adatkiadáshoz használnak, ha hibát jelentenek.

Ha a programjában megnyit egy fájlt olvasásra vagy írásra, akkor valószínűleg az első szabad azonosítót fogja megkapni, ami a 3-as számot jelenti.

A fájl leírók listáját bármelyik folyamatnál megtekintheti, ha tudja a PID-jét.

Például nyissunk meg egy bash konzolt és nézzük meg a folyamatunk PID-jét.

[user@localhost ]$ echo $$
15771

A második konzolban futtassuk ezt:

[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

A 255-ös számú fájl leírót nyugodtan figyelmen kívül hagyhatja e cikk keretein belül, mivel azt a bash már a saját szükségleteihez nyitotta meg, nem a csatolt könyvtár.

Jelenleg mindhárom fájl leíró kapcsolatban áll a pszeudoterminál eszközzel. /dev/pts, de mégis manipulálhatjuk őket, például futtassuk a második konzolon:

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

És az első konzolon ezt fogjuk látni:

[user@localhost ]$ hello world

Átirányítás és csővezeték

Ezeket a három fájl leírót könnyen felülírhatja bármelyik folyamatban, akár bash-ben is, például egy csővezetéken (pipe), amely két folyamatot összekapcsol, nézzük meg:

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

Ön is futtathatja ezt a parancsot, strace -f és láthatja, mi történik belül, de röviden elmondom.

A szülő folyamatunk, a bash PID 15771, elemzi a parancsunkat és megérti, hány parancsot szeretnénk futtatni, ebben az esetben kettőt: cat és sleep. A bash tudja, hogy kettő gyermekfolyamatot kell létrehoznia, és ezeket egy csővezetékkel kell összekapcsolnia. Összességében a bash-nak 2 gyermekfolyamatra és egy csővezetékre van szüksége.

A gyermekfolyamatok létrehozása előtt a bash végrehajt egy rendszerhívást, cső és új fájl leírókat kap a csővezetékre fenntartott ideiglenes pufferhez, de ez a puffer jelenleg még nem köti össze a két gyermekfolyamatot.

A szülő folyamat számára úgy tűnik, hogy a csővezetéke már létezik, míg a gyermekfolyamatok még nem:

PID    parancs
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

Ezután a rendszerhívással clone a bash két gyermekfolyamatot hoz létre, így a három folyamatunk így néz ki:

PID    parancs
15771  bash
lrwx------ 1 felhasználó felhasználó 64 Okt  7 15:42 0 -> /dev/pts/21
lrwx------ 1 felhasználó felhasználó 64 Okt  7 15:42 1 -> /dev/pts/21
lrwx------ 1 felhasználó felhasználó 64 Okt  7 15:42 2 -> /dev/pts/21
lrwx------ 1 felhasználó felhasználó 64 Okt  7 15:42 3 -> pipe:[253543032]
lrwx------ 1 felhasználó felhasználó 64 Okt  7 15:42 4 -> pipe:[253543032]
lrwx------ 1 felhasználó felhasználó 64 Okt  7 15:42 255 -> /dev/pts/21
PID    parancs
9004  bash
lrwx------ 1 felhasználó felhasználó 64 Okt  7 15:57 0 -> /dev/pts/21
lrwx------ 1 felhasználó felhasználó 64 Okt  7 15:57 1 -> /dev/pts/21
lrwx------ 1 felhasználó felhasználó 64 Okt  7 15:57 2 -> /dev/pts/21
lrwx------ 1 felhasználó felhasználó 64 Okt  7 15:57 3 -> pipe:[253543032]
lrwx------ 1 felhasználó felhasználó 64 Okt  7 15:57 4 -> pipe:[253543032]
lrwx------ 1 felhasználó felhasználó 64 Okt  7 15:57 255 -> /dev/pts/21
PID    parancs
9005  bash
lrwx------ 1 felhasználó felhasználó 64 Okt  7 15:57 0 -> /dev/pts/21
lrwx------ 1 felhasználó felhasználó 64 Okt  7 15:57 1 -> /dev/pts/21
lrwx------ 1 felhasználó felhasználó 64 Okt  7 15:57 2 -> /dev/pts/21
lrwx------ 1 felhasználó felhasználó 64 Okt  7 15:57 3 -> pipe:[253543032]
lrwx------ 1 felhasználó felhasználó 64 Okt  7 15:57 4 -> pipe:[253543032]
lrwx------ 1 felhasználó felhasználó 64 Okt  7 15:57 255 -> /dev/pts/21

Ne felejtsük el, hogy a clone a folyamatot klónozza, beleértve az összes fájl leíróját, ezért a szülőfolyamatban és a gyermekfolyamatokban ezek azonosak lesznek. A szülőfolyamat feladata a 15771-es PID-jű gyermekfolyamatok figyelése, ezért egyszerűen vár a gyermekektől érkező válaszra.

Ezért a pipe-ra nincs szüksége, és bezárja a 3-as és 4-es számú fájl leírókat.

Az első gyermekfolyamatban a bash, PID 9004, a rendszerhívással dup2, megváltoztatja a STDOUT fájl leíróját 1-es számra, hogy a pipe-ra mutasson, amely jelen esetben a 3-as szám. Így minden, amit a 9004 PID-ű első gyermekfolyamat a STDOUT-ba ír, automatikusan a pipe bufferébe kerül.

A második gyermekfolyamatban, a PID 9005-ön a bash a dup2-vel megváltoztatja a STDIN fájl leíróját 0-ra. Most minden, amit a 9005 PID-jű második bash olvas, a pipe-ból olvas.

Ezután a gyermekfolyamatokban is bezárják a 3-as és 4-es számú fájl leírókat, mivel már nem használják őket.

A 255-ös fájl leírót szándékosan figyelmen kívül hagyom, ezt a bash belső használatára használja, és a gyermekfolyamatokban szintén bezárják.

Ezután az első gyermekfolyamatban a PID 9004-gyel a bash indítja a rendszerhívással exec a végrehajtható fájlt, amelyet a parancssorban megadtunk, jelen esetben ez /usr/bin/cat.

A második gyermekfolyamatban a PID 9005-ön a bash elindítja a második végrehajtható fájlt, amelyet megadtunk, jelen esetben ez /usr/bin/sleep.

A rendszerváltoztatás nem zárja le a fájldeszkriptorokat, ha azokat nem O_CLOEXEC flagekkel nyitották meg az open hívás során. Az esetünkben a futtatható fájlok elindítása után az összes jelenlegi fájldeszkriptor megmarad.

Ellenőrizzük a konzolon:

[user@localhost ]$ pgrep -P 15771
9004
9005
[user@localhost ]$ ls -lah /proc/15771/fd/
total 0
dr-x------ 2 user user  0 Okt  7 15:42 .
dr-xr-xr-x 9 user user  0 Okt  7 15:42 ..
lrwx------ 1 user user 64 Okt  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Okt  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Okt  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Okt  7 15:42 255 -> /dev/pts/21
[user@localhost ]$ ls -lah /proc/9004/fd
total 0
dr-x------ 2 user user  0 Okt  7 15:57 .
dr-xr-xr-x 9 user user  0 Okt  7 15:57 ..
lrwx------ 1 user user 64 Okt  7 15:57 0 -> /dev/pts/21
l-wx------ 1 user user 64 Okt  7 15:57 1 -> pipe:[253543032]
lrwx------ 1 user user 64 Okt  7 15:57 2 -> /dev/pts/21
lr-x------ 1 user user 64 Okt  7 15:57 3 -> /dev/zero
[user@localhost ]$ ls -lah /proc/9005/fd
total 0
dr-x------ 2 user user  0 Okt  7 15:57 .
dr-xr-xr-x 9 user user  0 Okt  7 15:57 ..
lr-x------ 1 user user 64 Okt  7 15:57 0 -> pipe:[253543032]
lrwx------ 1 user user 64 Okt  7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Okt  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

Ahogy látja, a cső egyedi száma mindkét folyamatban megegyezik. Így kapcsolat áll fenn a két különböző folyamat között, melynek közös szülője van.

Annak, aki nem ismeri a bash által használt rendszerváltoztatásokat, javaslom, hogy indítsa el a parancsokat strace segítségével, és nézze meg, mi történik belül, például így:

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

Térjünk vissza a lemezhely hiányának problémájához és az adatok mentésének megpróbálásához a folyamat újraindítása nélkül. Írjunk egy kis programot, amely másodpercenként körülbelül 1 megabájt adatot ír a lemezre. Ha bármilyen okból nem tudjuk az adatokat a lemezre írni, egyszerűen figyelmen kívül hagyjuk ezt, és újra próbálkozunk egy másodperc múlva. Az alábbi példában Pythont használok, de használhat bármilyen más programozási nyelvet is.

[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

Futtassuk a programot, és nézzük meg a fájl deszkriptorokat

[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

Ahogy látjuk, három alapértelmezett fájl leíróval rendelkezünk, és van egy extra, amit megnyitottunk. Ellenőrizzük a fájl méretét:

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

Az adatok íródnak, próbáljuk meg megváltoztatni a fájl jogosultságait:

[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

Látjuk, hogy az adatok továbbra is íródnak, bár a felhasználónknak nincs joga a fájl írására. Próbáljuk meg törölni:

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

Hová íródnak az adatok? És egyáltalán íródnak? Ellenőrizzük:

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

Igen, a fájl leíró még mindig létezik, és dolgozhatunk vele, mint a régi fájlunkkal; olvashatjuk, törölhetjük és másolhatjuk.

Nézzük meg a fájl méretét:

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

A fájl mérete 19923457. Próbáljuk meg törölni a fájlt:

[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

Ahogy látjuk, a fájl mérete csak növekszik, és a törlés nem működött. Nézzük meg a dokumentációt a rendszerhívásokkal kapcsolatban. openHa fájl megnyitásakor O_APPEND jelzőt használunk, akkor minden íráskor az operációs rendszer ellenőrzi a fájl méretét, és az adatokat a fájl végére írja, atomikusan. Ez lehetővé teszi, hogy több szál vagy folyamat írjon ugyanabba a fájlba. De a kódunkban nem használjuk ezt a jelzőt. Csak akkor láthatunk más fájlméretet az lsof nagyszámú törlés után, ha a fájlt hozzáfűzésre nyitjuk meg, ezért a kódunkban a következő helyett

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

használni kell

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

Ellenőrizzük a „w” flag-gel

[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

és a "a" zászlóval

[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

Futtasd a már elindított folyamatot

Gyakran a programozók program készítése és tesztelése során használnak debuggereket (például GDB) vagy különböző naplózási szinteket az alkalmazásban. A Linux lehetőséget biztosít arra, hogy ténylegesen írjunk és módosítsunk egy már futó programot, például változók értékeinek megváltoztatásával, breakpointok beállításával stb.

Visszatérve az eredeti kérdéshez, amely a fájl írásához szükséges lemezhely hiányáról szólt, próbáljuk meg szimulálni a problémát.

Hozzuk létre a fájlt a részünkhöz, amelyet külön lemezként be fogunk csatlakoztatni:

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

Hoztunk létre fájlrendszert:

[user@localhost ~]$ mkfs.ext4 ~/tempfile_for_article.dd
mke2fs 1.42.9 (28-Dec-2013)
/home/user/tempfile_for_article.dd nem blokk különleges eszköz.
Tovább halad? (y,n) y
...
Superblokkok és fájlrendszer számviteli információk írása: kész
[user@localhost ~]$

Csatlakoztassuk a fájlrendszert:

[user@localhost ~]$ sudo mount ~/tempfile_for_article.dd /mnt/
[sudo] jelszó a felhasználónak: 
[user@localhost ~]$ df -h | grep mnt
/dev/loop0      8.7M  172K  7.9M   3% /mnt

Létrehozunk egy könyvtárat a saját tulajdonosunkkal:

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

Megnyitjuk a fájlt csak írásra a programunkban:

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

Indítás

[user@localhost ]$ python openforwrite.py 

Várunk néhány másodpercet

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

Tehát megkaptuk a cikk elején leírt problémát. Szabad hely 0, elfoglalt 100%.

Emlékszünk arra, hogy a feladat feltételei szerint nagyon fontos adatokat próbálunk írni, amelyeket nem lehet elveszíteni. És közben javítanunk kell a szolgáltatást anélkül, hogy újraindítanánk a folyamatot.

Tegyük fel, hogy van hely a lemezen, de egy másik partíción, például a /home-on.

Próbáljuk meg "szökőáron" újraírni a kódunkat.

Nézzük meg a PID-jét annak a folyamatnak, amely minden lemezhelyet elhasznált:

[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

Kapcsolódjunk a folyamathoz a gdb-n keresztül

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

Nézzük meg az összes megnyitott fájl leíróit:

(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

Nézzük meg a 3-as számú fájl leíróhoz tartozó információkat, ami minket érdekel

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

Emlékezve arra, hogy milyen rendszerhívást hajt végre a Python (lásd fent, ahol elindítottuk a strace-t és megtaláltuk az open hívást), miközben a fájl megnyitására vonatkozó kódunkat feldolgozzuk, ugyanezt tesszük saját folyamatunk nevében, de az O_WRONLY|O_CREAT|O_TRUNC biteket numerikus értékre kell cserélnünk. Ehhez megnyitjuk a kernel forrásait, például itt és megnézzük, hogy melyik flag mit jelent

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

Összegyűjtjük az összes értéket egybe, kapunk 00001101

Futtatjuk a hívásunkat a gdb-ből

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

Így egy új fájl leíróval rendelkezünk, amelynek száma 4, és új fájlt nyitottunk meg egy másik partíción, ellenőrizzük:

(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

Emlékezünk a pipe példára — hogyan változtatja meg a bash a fájl leírókat, és már megtanultuk a dup2 rendszerhívást.

Megpróbálunk egy fájl leírót egy másikra cserélni

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

Ellenőrizzük:

(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

Bezárjuk a 4-es fájl leírót, mivel nincs rá szükségünk:

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

És kilépünk a gdb-ből

(gdb) quit
Aktív hibakeresési munkamenet zajlik.

    Az alárendelt 1 [folyamat 10078] le lesz választva.

Mégis kilépni? (y vagy n) y
Leválás a programtól: /usr/bin/python2.7, folyamat 10078

Ellenőrizzük az új fájlt:

[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

Ahogy látjuk, az adatok új fájlba íródnak, ellenőrizzük a régit:

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

Az adatok nem vesztek el, az alkalmazás működik, a naplók egy új helyre íródnak.

Kicsit bonyolítsuk a feladatot

Tegyük fel, hogy az adatok fontosak számunkra, de egyik partíción sincs szabad helyünk, és nem tudunk diszket csatlakoztatni.

Amit tehetünk, az az, hogy valahova átirányítjuk az adatainkat, például egy pipe-ba, majd az abból érkező adatokat egy hálózaton keresztül egy programba, például a netcat-be irányítjuk.
Létrehozhatunk névlegesen pipe-ot az mkfifo parancs segítségével. Ez pszeudo-fájlt hoz létre a fájlrendszeren, még akkor is, ha nincs szabad hely rajta.

Újraindítjuk az alkalmazást, és ellenőrizzük:

[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 Okt  8 11:27 .
dr-xr-xr-x 9 user user  0 Okt  8 11:27 ..
lrwx------ 1 user user 64 Okt  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Okt  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Okt  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Okt  8 11:28 3 -> /mnt/logs/123.txt
[user@localhost ~]$ df -h | grep mnt
/dev/loop0      8.7M  8.0M     0 100% /mnt

Nincs hely a lemezen, de sikeresen létrehozzuk a név szerinti csövet:

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

Most valahogy be kell csomagolnunk az összes adatot, ami ezen a csövön keresztül a másik szerverre kerül, erre a netcat tökéletesen alkalmas.

A remote-server.example.com szerveren indítsuk el

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

A problémás szerverünkön indítsuk el egy külön terminálban

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

Most minden adat, ami a csőbe kerül automatikusan stdin-be kerül a netcat-ben, amely továbbítja őket a hálózaton a 7777-es porton.

Ami hátra van, az az, hogy elkezdjük írni az adatainkat ebbe a név szerinti csőbe.

Már van egy futó alkalmazásunk:

[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 Okt  8 11:27 .
dr-xr-xr-x 9 user user  0 Okt  8 11:27 ..
lrwx------ 1 user user 64 Okt  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Okt  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Okt  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Okt  8 11:28 3 -> /mnt/logs/123.txt

Az összes határ jel közül csak az O_WRONLY szükséges, mivel a fájl már létezik, és nem kell törölnünk.

[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

Ellenőrizzük a távoli szervert remote-server.example.com

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

Az adatok áramlanak, ellenőrizzük a problémás szervert

[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

Az adatok elmentődtek, a probléma megoldódott.

Ezúton üdvözlöm a Degiro munkatársait.
Hallgassátok a Radio-T podcastokat.

Mindenkinek jókedvet.

Házi feladatként azt javaslom, gondolkozzatok el, mi lesz a folyamat fájleíróiban, ha futtatjátok a következő parancsot:

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

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster