Deskryptor pliku w systemie Linux z przykładami

Kiedyś podczas rozmowy kwalifikacyjnej zapytano mnie, co zrobisz, jeśli okaże się, że usługa nie działa z powodu braku miejsca na dysku?

Oczywiście odpowiedziałem, że zobaczę, co jest zajęte w tym miejscu i jeśli to możliwe, posprzątam to miejsce.
Następnie ankieter zapytał, co jeśli na partycji nie ma wolnego miejsca, ale Ty też nie widzisz żadnych plików, które zajmowałyby całe miejsce?

Na to powiedziałem, że zawsze możesz spojrzeć na otwarte deskryptory plików, na przykład za pomocą polecenia lsof, i dowiedzieć się, która aplikacja zajęła całe dostępne miejsce, a następnie możesz działać w zależności od okoliczności, w zależności od tego, czy dane są potrzebne .

Prowadzący wywiad przerwał mi ostatnie słowo, dodając do swojego pytania: „Załóżmy, że nie potrzebujemy danych, to tylko dziennik debugowania, ale aplikacja nie działa, ponieważ nie może napisać debugowania”?

„OK” – odpowiedziałem – „możemy wyłączyć debugowanie w konfiguracji aplikacji i uruchomić ją ponownie”.
Rozmówca sprzeciwił się: „Nie, nie możemy zrestartować aplikacji, nadal mamy zapisane w pamięci ważne dane, a ważni klienci są połączeni z samą usługą, której nie możemy wymusić ponownego połączenia”.

„okej” – powiedziałem – „jeśli nie możemy ponownie uruchomić aplikacji, a dane nie są dla nas ważne, to możemy po prostu wyczyścić ten otwarty plik poprzez deskryptor pliku, nawet jeśli nie widzimy go w poleceniu ls w systemie plików.”

Osoba przeprowadzająca wywiad była zadowolona, ​​ale ja nie.

Pomyślałem wtedy: dlaczego osoba sprawdzająca moją wiedzę nie sięga głębiej? Ale co, jeśli mimo wszystko dane są ważne? Co się stanie, jeśli nie będziemy mogli ponownie uruchomić procesu, a proces zapisuje dane w systemie plików na partycji, na której nie ma wolnego miejsca? A co jeśli nie możemy stracić nie tylko danych, które zostały już zapisane, ale także tych, które ten proces zapisuje lub próbuje zapisać?

Tuzik

Na początku mojej kariery próbowałem stworzyć małą aplikację, która musiała przechowywać informacje o użytkowniku. I wtedy pomyślałem, jak dopasować użytkownika do jego danych. Na przykład mam Iwanowa Iwana Iwanowicza, a on ma pewne informacje, ale jak mogę się z nimi zaprzyjaźnić? Od razu mogę zaznaczyć, że pies o imieniu „Tuzik” należy do tego właśnie Iwana. Ale co, jeśli zmieni imię i zamiast Iwana stanie się na przykład Olyą? Wtedy okaże się, że nasza Ola Iwanowna Iwanowa nie będzie już miała psa, a nasz Tuzik nadal będzie należeć do nieistniejącego Iwana. Baza danych, która nadawała każdemu użytkownikowi unikalny identyfikator (ID), pomogła rozwiązać ten problem, a mój Tuzik był powiązany z tym identyfikatorem, który w rzeczywistości był tylko numerem seryjnym. Tym samym właściciel tuzika miał numer identyfikacyjny 2, w pewnym momencie pod tym identyfikatorem znajdował się Iwan, a następnie pod tym samym identyfikatorem znalazła się Ola. Problem ludzkości i hodowli zwierząt został praktycznie rozwiązany.

Deskryptor pliku

Problem pliku i programu obsługującego ten plik jest w przybliżeniu taki sam jak u naszego psa i człowieka. Załóżmy, że otworzyłem plik o nazwie ivan.txt i zacząłem wpisywać w nim słowo tuzik, ale udało mi się wpisać tylko pierwszą literę „t” w pliku i ktoś zmienił nazwę tego pliku, na przykład na olya.txt. Ale plik pozostaje ten sam i nadal chcę w nim nagrać swojego asa. Za każdym razem, gdy plik jest otwierany przez wywołanie systemowe koncepcja w dowolnym języku programowania otrzymuję unikalny identyfikator, który wskazuje mi plik, ten identyfikator jest deskryptorem pliku. I nie ma żadnego znaczenia, co i kto dalej z tym plikiem zrobi, można go usunąć, zmienić jego nazwę, zmienić właściciela lub odebrać prawa do odczytu i zapisu, nadal będę miał dostęp do niego, ponieważ w momencie otwierania pliku miałem uprawnienia do jego odczytania i/lub zapisu i udało mi się rozpocząć z nim pracę, co oznacza, że ​​muszę to robić dalej.

W systemie Linux biblioteka libc otwiera 3 pliki deskryptorów dla każdej działającej aplikacji (procesu), ponumerowane 0,1,2. Więcej informacji można znaleźć w linkach człowiek stdio и standardowe wyjście człowieka

  • Deskryptor pliku 0 nazywa się STDIN i jest powiązany z danymi wejściowymi aplikacji
  • Deskryptor pliku 1 nazywa się STDOUT i jest używany przez aplikacje do wysyłania danych, takich jak polecenia drukowania
  • Deskryptor pliku 2 nazywa się STDERR i jest używany przez aplikacje do wysyłania komunikatów o błędach.

Jeśli w swoim programie otworzysz dowolny plik do odczytu lub zapisu, najprawdopodobniej otrzymasz pierwszy darmowy identyfikator i będzie on numer 3.

Listę deskryptorów plików można wyświetlić dla dowolnego procesu, jeśli znasz jego PID.

Na przykład otwórzmy konsolę bash i spójrzmy na PID naszego procesu

[user@localhost ]$ echo $$
15771

W drugiej konsoli uruchommy

[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

Na potrzeby tego artykułu możesz bezpiecznie zignorować deskryptor pliku nr 255; został on otwarty na jego potrzeby przez sam bash, a nie przez połączoną bibliotekę.

Teraz wszystkie 3 pliki deskryptorów są powiązane z pseudourządzeniem terminalowym /dev/pt, ale nadal możemy nimi manipulować, na przykład uruchamiać je w drugiej konsoli

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

A w pierwszej konsoli zobaczymy

[user@localhost ]$ hello world

Przekieruj i potoku

Możesz łatwo zastąpić te 3 pliki deskryptorów w dowolnym procesie, w tym w bashu, na przykład poprzez potok łączący dwa procesy, zobacz

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

Możesz uruchomić to polecenie samodzielnie strace -f i zobaczę co się dzieje w środku, ale opowiem Wam pokrótce.

Nasz nadrzędny proces bash z PID 15771 analizuje nasze polecenie i dokładnie rozumie, ile poleceń chcemy uruchomić, w naszym przypadku są dwa z nich: cat i Sleep. Bash wie, że musi utworzyć dwa procesy potomne i połączyć je w jeden potok. W sumie bash będzie potrzebował 2 procesów potomnych i jednego potoku.

Bash uruchamia wywołanie systemowe przed utworzeniem procesów potomnych rura i otrzymuje nowe deskryptory plików w tymczasowym buforze potoku, ale ten bufor nie łączy jeszcze naszych dwóch procesów potomnych.

W przypadku procesu nadrzędnego wygląda na to, że istnieje już potok, ale nie ma jeszcze procesów potomnych:

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

Następnie użyj wywołania systemowego klonować bash tworzy dwa procesy potomne, a nasze trzy procesy będą wyglądać następująco:

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

Nie zapominaj, że klon klonuje proces wraz ze wszystkimi deskryptorami plików, dzięki czemu będą one takie same w procesie nadrzędnym i podrzędnym. Zadaniem procesu nadrzędnego o PID 15771 jest monitorowanie procesów potomnych, więc po prostu czeka na odpowiedź od potomków.

Dlatego nie potrzebuje potoku i zamyka deskryptory plików o numerach 3 i 4.

W pierwszym procesie potomnym bash z PID 9004 wywołanie systemowe dup2, zmienia nasz deskryptor pliku STDOUT numer 1 na deskryptor pliku wskazujący na potok, w naszym przypadku jest to numer 3. Zatem wszystko, co pierwszy proces potomny zapisuje do STDOUT, automatycznie wyląduje w buforze potoku.

W drugim procesie potomnym z PID 9005, bash używa dup2 do zmiany deskryptora pliku STDIN o numerze 0. Teraz wszystko, co przeczyta nasz drugi bash z PID 9005, zostanie odczytane z potoku.

Następnie deskryptory plików o numerach 3 i 4 są również zamykane w procesach potomnych, ponieważ nie są już używane.

Celowo ignoruję deskryptor pliku 255; jest on używany do celów wewnętrznych przez sam bash i zostanie również zamknięty w procesach potomnych.

Następnie, w pierwszym procesie potomnym z PID 9004, bash zaczyna używać wywołania systemowego exec plik wykonywalny, który podaliśmy w wierszu poleceń, w naszym przypadku jest to /usr/bin/cat.

W drugim procesie potomnym z PID 9005 bash uruchamia drugi określony przez nas plik wykonywalny, w naszym przypadku /usr/bin/sleep.

Wywołanie systemowe exec nie zamyka uchwytów plików, chyba że zostały one otwarte z flagą O_CLOEXEC w momencie wykonania wywołania open. W naszym przypadku po uruchomieniu plików wykonywalnych wszystkie aktualne deskryptory plików zostaną zapisane.

Sprawdź w konsoli:

[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 widać unikalny numer naszej rury jest taki sam w obu procesach. W ten sposób mamy połączenie między dwoma różnymi procesami z tym samym rodzicem.

Tym, którzy nie są zaznajomieni z wywołaniami systemowymi używanymi przez bash, gorąco polecam uruchomienie poleceń przez strace i sprawdzenie, co dzieje się wewnętrznie, na przykład tak:

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

Wróćmy do naszego problemu z małą ilością miejsca na dysku i próbą zapisania danych bez ponownego uruchamiania procesu. Napiszmy mały program, który będzie zapisywał na dysk około 1 megabajt na sekundę. Co więcej, jeśli z jakiegoś powodu nie udało nam się zapisać danych na dysk, po prostu to zignorujemy i za sekundę spróbujemy ponownie zapisać dane. W przykładzie, w którym używam Pythona, możesz użyć dowolnego innego języka programowania.

[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

Uruchommy program i spójrzmy na deskryptory plików

[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 widać, mamy 3 standardowe deskryptory plików i jeszcze jeden, który otworzyliśmy. Sprawdźmy rozmiar pliku:

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

Dane są zapisywane, próbujemy zmienić uprawnienia do pliku:

[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

Widzimy, że dane są nadal zapisywane, chociaż nasz użytkownik nie ma uprawnień do zapisu do pliku. Spróbujmy to usunąć:

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

Gdzie są zapisane dane? I czy w ogóle są pisane? Sprawdzamy:

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

Tak, nasz deskryptor pliku nadal istnieje i możemy go traktować jak nasz stary plik, możemy go czytać, usuwać i kopiować.

Spójrzmy na rozmiar pliku:

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

Rozmiar pliku to 19923457. Spróbujmy wyczyścić plik:

[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 widać, rozmiar pliku tylko rośnie, a nasz bagażnik nie działał. Przyjrzyjmy się dokumentacji wywołań systemowych koncepcja. Jeśli przy otwieraniu pliku użyjemy flagi O_APPEND, to przy każdym zapisie system operacyjny sprawdza rozmiar pliku i dopisuje dane do samego końca pliku i robi to atomowo. Dzięki temu wiele wątków lub procesów może zapisywać w tym samym pliku. Ale w naszym kodzie nie używamy tej flagi. Inny rozmiar pliku w lsof po trunku możemy zobaczyć tylko wtedy, gdy otworzymy plik do dodatkowego zapisu, co oznacza zamiast tego w naszym kodzie

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

musimy położyć

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

Sprawdzanie za pomocą flagi „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 z flagą „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

Programowanie już działającego procesu

Często programiści tworząc i testując programy korzystają z debuggerów (np. GDB) lub różnych poziomów logowania w aplikacji. Linux zapewnia możliwość faktycznego napisania i zmiany już działającego programu, na przykład zmiany wartości zmiennych, ustawienia punktu przerwania itp. itp.

Wracając do pierwotnego pytania o brak miejsca na dysku do zapisania pliku, spróbujmy zasymulować problem.

Stwórzmy plik dla naszej partycji, który zamontujemy jako osobny dysk:

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

Stwórzmy system plików:

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

Zamontuj system plików:

[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

Tworzymy katalog z naszym właścicielem:

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

Otwórzmy plik do zapisu tylko w naszym programie:

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

Biegnij

[user@localhost ]$ python openforwrite.py 

Czekamy kilka sekund

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

Mamy więc problem opisany na początku tego artykułu. Wolne miejsce 0, 100% zajęte.

Pamiętamy, że zgodnie z warunkami zadania staramy się zapisać bardzo ważne dane, których nie można utracić. Jednocześnie musimy naprawić usługę bez ponownego uruchamiania procesu.

Załóżmy, że nadal mamy miejsce na dysku, ale na innej partycji, na przykład w /home.

Spróbujmy „przeprogramować w locie” nasz kod.

Spójrzmy na PID naszego procesu, który pochłonął całe miejsce na dysku:

[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

Połącz się z procesem poprzez gdb

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

Przyjrzyjmy się deskryptorom otwartych plików:

(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

Przyglądamy się informacji o deskryptorze pliku nr 3, który nas interesuje

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

Pamiętając, jakie wywołanie systemowe wykonuje Python (zobacz powyżej, gdzie uruchomiliśmy strace i znaleźliśmy wywołanie open), przetwarzając nasz kod w celu otwarcia pliku, sami robimy to samo w imieniu naszego procesu, ale potrzebujemy O_WRONLY|O_CREAT| Bity O_TRUNC są zastępowane wartością numeryczną. Aby to zrobić, otwórz na przykład źródła jądra tutaj i zobacz, które flagi są za co odpowiedzialne

#zdefiniuj O_WRONLY 00000001
#zdefiniuj O_CREAT 00000100
#zdefiniuj O_TRUNC 00001000

Łączymy wszystkie wartości w jedną, otrzymujemy 00001101

Uruchamiamy nasze wywołanie z gdb

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

Mamy więc nowy deskryptor pliku o numerze 4 i nowy otwarty plik na innej partycji, sprawdzamy:

(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

Pamiętamy przykład z potokiem - jak bash zmienia deskryptory plików i poznaliśmy już wywołanie systemowe dup2.

Próbujemy zastąpić jeden deskryptor pliku innym

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

Sprawdzanie:

(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

Zamykamy deskryptor pliku 4, ponieważ go nie potrzebujemy:

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

I wyjdź 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

Sprawdzanie nowego pliku:

[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 widać dane są zapisywane do nowego pliku, sprawdźmy stary:

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

Żadne dane nie giną, aplikacja działa, logi są zapisywane w nowej lokalizacji.

Skomplikujmy trochę zadanie

Wyobraźmy sobie, że dane są dla nas ważne, ale na żadnej z partycji nie mamy miejsca na dysku i nie możemy podłączyć dysku.

Jedyne co możemy zrobić to przekierować gdzieś nasze dane, np. do potoku i z kolei przekierować dane z potoku do sieci poprzez jakiś program, np. netcat.
Możemy utworzyć nazwany potok za pomocą polecenia mkfifo. Utworzy pseudoplik w systemie plików, nawet jeśli nie ma na nim wolnego miejsca.

Uruchom ponownie aplikację i sprawdź:

[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

Nie ma miejsca na dysku, ale pomyślnie tworzymy tam nazwany potok:

[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 musimy w jakiś sposób zawinąć wszystkie dane, które trafiają do tego potoku, do innego serwera za pośrednictwem sieci; nadaje się do tego ten sam netcat.

Na serwerze zdalny-serwer.example.com uruchamiamy

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

Na naszym problematycznym serwerze uruchamiamy w osobnym terminalu

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

Teraz wszystkie dane, które trafią do potoku, automatycznie przejdą na standardowe wejście w Netcat, które wyśle ​​je do sieci na porcie 7777.

Wszystko, co musimy zrobić, to zacząć zapisywać nasze dane w tym nazwanym potoku.

Mamy już uruchomioną aplikację:

[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

Ze wszystkich flag potrzebujemy tylko O_WRONLY, ponieważ plik już istnieje i nie musimy go czyścić

[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

Sprawdzanie serwera zdalnego zdalny-serwer.example.com

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

Dane przychodzą, sprawdzamy serwer powodujący problemy

[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

Dane zostały zapisane, problem rozwiązany.

Korzystam z okazji, aby przywitać się z moimi kolegami z Degiro.
Słuchaj podcastów Radia-T.

Wszystko dobrze.

W ramach pracy domowej sugeruję przemyślenie, co będzie się działo w procesie deskryptorów plików cat i Sleep, jeśli uruchomisz następującą komendę:

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

Źródło: www.habr.com

Dodaj komentarz