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