no space left on device...

Regularnie usuwasz niepotrzebne pliki z dysku, wszystkie logi również rotujesz, ale czasami zdarza Ci się widzieć 0% wolnego miejsca na dysku pomimo tego, że żaden dostępny plik czy katalog nie zajmuje jakoś specjalnie dużo. Jednym słowem czary... ;-) ale, że już dawno wyrośliśmy ze świętego mikołaja i wróżek, to zdradzę tajemnicę, że to niezupełnie czary, a raczej  błąd w konfiguracji. Tak więc, usiądźcie wygodnie, weźcie kawę do ręki i lecimy.

W momencie kiedy czytasz/piszesz z/do pliku system operacyjny otwiera deskryptor i przypisuje plik do niego, tak więc mamy uchwyt do którego sięga podczas każdej operacji. Piszemy coś do pliku... piszemy... piszemy... i nagle plik usuwamy (rm -rf plik), system nie zamknie deskryptora ponieważ nie otrzymał takiego polecenia (np. funkcja systemowa close()), tak więc pomimo tego, że naszego pliku już nie ma na dysku to deskryptor nadal pozostał otwarty i nadal możemy z niego korzystać (pisać i czytać). Przekładając to na najczęstszą sytuację posłużę się syslogiem oraz logami. Widzimy, że mamy dużo zajętego w miejsca w /var/log/, wypadałoby więc skasować najstarsze logi, tak więc rm -rf /var/log/stare_logi.txt no i jest super, trochę miejsca się zwolniło i od razu nam lepiej. Po jakimś czasie problem się znów pojawia, z tym, że już nie ma czego kasować, du -sh /var/log/ nie pokazuje dużych wartości, a w pozostałych katalogach również pustki. Sytuacja trochę dziwna, ale wbrew pozorom częsta i łatwa do odtworzenia. Syslog jest daemonem który zapisuje otrzymane dane do plików, tak więc otwiera deskryptory do plików z logami, my usuwając pliki, nie zamykamy poprawnie deskryptorów, no i syslog nadal loguje jak gdyby nigdy nic do tych plików pomimo tego, że fizycznie ich nie widać.

Jak więc sprawdzić czy mamy takie pliki? Jest na to bardzo prosty sposób, pokażę na pewnym przykładzie:

Mamy więc taki przykładowy skrypt PERL'owy, otwieramy plik (plik.log), zostaje utworzony deskryptor do pliku, w nieskończonej pętli zapisujemy do pliku ciąg znaków "www.varlog.pl", a na STDOUT wypluwamy znak kropki i odczekujemy 1 milisekundę. Celowo wywołanie close(PLIK), czyli zamknięcie deskryptora znajduje się poza pętlą. Uruchamiamy skrypt, na ekranie pojawiają się nam kropki, a plik.log wraz z upływem czasu rośnie:

jamzed@katha:~/varlog$ while [ 1 ]; do ls -la plik.log; sleep 1; done
-rw-r--r-- 1 jamzed jamzed 184320 2010-03-19 21:28 plik.log
-rw-r--r-- 1 jamzed jamzed 196608 2010-03-19 21:28 plik.log
-rw-r--r-- 1 jamzed jamzed 212992 2010-03-19 21:28 plik.log
-rw-r--r-- 1 jamzed jamzed 225280 2010-03-19 21:29 plik.log

Skasujmy teraz plik.log (rm -rf plik.log),

jamzed@katha:~/varlog$ rm -rf plik.log
jamzed@katha:~/varlog$ ls -la plik.log
ls: cannot access plik.log: No such file or directory

Wszystko super, pozbyliśmy się pliku, ale nasz skrypt PERL'owy (space.pl) nadal pisze do plik.log, sprawdźcie sami, że wolnego miejsca na dysku robi się coraz mniej ;-)

Ok... w tym przypadku wiemy jaki to jest plik, oraz jaki proces trzyma deskryptor, wystarczy taki proces zabić, lub przerwać CTRL+C, wtedy deskryptor zostanie zamknięty a miejsce na dysku zwolni się, ale co w sytuacji kiedy nie znamy procesu, ani pliku... użyjmy lsof'a. Narzędzie lsof, listuje nasze aktywne deskryptory, przy okazji  informując nas który został usunięty ;-)

jamzed@katha:~/varlog$ lsof -n|grep deleted
space.pl  22817   jamzed    3w      REG    8,2  3235840 786475 /home/jamzed/varlog/plik.log (deleted)

No i wszystko pasuje, proces space.pl pisze do 3 deskryptora czyli pliku (plik.log), spróbujmy zatem skillować space.pl i sprawdźmy co się stanie z ilością wolnego miejsca:

/dev/sda2             63440968  33750780  26467552 57% /home

oraz po skillowaniu procesu (kill -9 22817)

/dev/sda2             63440968  33746392  26471940 57% /home

Zwolniły się 4MB czyli rozmiar naszego plik.log.

Tak więc, jak mówi stare admińskie przysłowie, nic w przyrodzie nie ginie, czyli pamiętajcie o restarcie albo wysyłaniu HUP'a (o ile nasza aplikacja obsługuje taki sygnał) do procesu po skasowaniu plików które miał otwarte ;-)