rm -rf не унищожава това, което мислиш
Какво реално остава след изтриването, защо мястото понякога не се освобождава и как файл без име може още да е жив
Всеки, който е работил достатъчно дълго с Linux, рано или късно стига до един от онези моменти, в които системата изглежда сякаш нарушава собствената си логика. Изтриваш файл. ls го няма. Пускаш df и дисковото пространство не се е върнало. Или махаш файл, а след минута той пак е на същото място. В друг случай човек е убеден, че rm -rf е унищожил чувствителни данни, а всъщност е премахнал само текущия път до тях.
Проблемът не е в rm. Проблемът е в модела, с който човек си представя файла.
За повечето хора файлът е име: /var/log/application.log. За Unix това име е само един от начините да се стигне до файловия обект. И ако не разделим pathname, directory entry, inode, open file descriptor и физическото съдържание върху носителя, думата "изтрит" започва да означава пет различни неща едновременно.
Тази тема е за разглобяването точно на това объркване.
Името е адресът, не къщата
Едно изречение държи цялата тема:
Името на файла не е самият файлов обект.
При типична Unix/Linux файлова система опростеният модел изглежда така:
Code:
application.log
|
v
directory entry
|
v
inode / filesystem object
|
+---- owner
+---- permissions
+---- timestamps
+---- size
+---- link count
|
+----> информация за разположението на съдържанието
Това е учебна схема, не физически чертеж на всяка съществуваща файлова система. Съвременните файлови системи могат да използват extents, Copy-on-Write дървета, checksums, snapshots и различни нива на индирекция. Но идеята остава: името в директорията е вход към обекта, не самият обект.
Най-близката аналогия е адресът на къща. Ако махнеш табелата с адреса, не следва автоматично, че сградата се е изпарила. Просто един конкретен начин да стигнеш до нея вече не съществува.
Оттук следват три отделни въпроса, които обикновено се задават като един: съществува ли pathname, съществува ли файловият обект и съществуват ли старите физически данни някъде в storage слоя.
Ако ги смесиш, Linux изглежда противоречив. Ако ги разделиш, поведението му става почти досадно логично.
Какво всъщност прави rm
При обикновен файл rm в крайна сметка иска от операционната система да премахне името от файловия namespace чрез unlink семантика. Ако това е последното име към обекта, link count пада до нула.
Но дори тогава обектът не е задължително готов за незабавно освобождаване, защото може да съществуват други активни референции. Най-познатият случай е процес, който вече е отворил файла и държи file descriptor. Друг случай е memory mapping.
Затова по-точният модел не е:
Code:
rm -> файлът изчезва
а:
Code:
премахва се име
|
v
намалява link count
|
v
проверява се дали обектът още има активни references
|
v
ако няма -> ресурсите могат да бъдат освободени
При директориите механизмът е различен и rm -r извършва рекурсивна работа по цялото дърво. Тоест rm -rf не е специална ядрена команда "унищожи всичко". Това е потребителски инструмент, който извършва поредица от файлови операции според структурата, която му подадеш.
Изтрих 20 GB лог, но мястото не се върна
Това е класическият production случай. Имаш 20 GB лог, махаш го, ls го няма, а df -h показва почти същото свободно място както преди.
Най-честата причина е, че процесът, който е писал в този лог, все още го държи отворен. След като файлът е отворен, процесът има file descriptor към съответния open file object и не е длъжен при всяко записване отново да търси pathname-а в директорията.
Опростено:
Code:
application
|
v
file descriptor 7
|
v
open file object
|
v
filesystem object
Directory entry вече може да е изчезнал, но процесът още има валидна референция.
Това е моментът, в който разговорното "файлът го няма" и техническото "файловият обект е освободен" се разделят.
В Unix един файл може да изгуби името си, без веднага да изгуби живота си.
lsof +L1: кой държи изтритото отворено
Първата практическа проверка е:
Code:
lsof +L1
Примерен резултат:
Code:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
nginx 1842 root 5w REG 8,1 20G 0 393221 /var/log/nginx/access.log (deleted)
Две полета са важни. NLINK = 0 означава, че вече няма directory link към този файлов обект. FD = 5w означава, че процесът държи file descriptor 5, отворен за запис.
Именно затова блоковете още се отчитат като използвани.
Това не е дефект на rm. Не е кеш, който някой ден ще се оправи. Не е файловата система, която е забравила да освободи диска. Процесът още използва обекта.
/proc/<PID>/fd: виждаш файла след като името му е умряло
Linux expose-ва file descriptor-ите на процесите през /proc/<PID>/fd/. При горния пример:
Code:
ls -l /proc/1842/fd/
може да покаже:
Code:
5 -> /var/log/nginx/access.log (deleted)
Pathname вече няма, но kernel-ът знае към кой отворен файлов обект сочи descriptor 5. При обикновен regular file и подходящи права понякога можем дори да копираме съдържанието през него:
Code:
cp /proc/1842/fd/5 recovered.log
Това е полезен recovery трик с точна уговорка: не е универсален метод за възстановяване на изтрити файлове. Резултатът зависи от типа на файловия обект, правата, състоянието на процеса и начина, по който descriptor-ът е отворен.
Но ако важен regular file е unlink-нат по грешка и процес все още го държи, /proc може да се окаже последната отворена врата към съдържанието му.
Как да си върнеш мястото, без да рестартираш половината сървър
Диагностиката е хубаво нещо, но в три през нощта въпросът е друг: как да се освободи дисковото пространство сега. Три подхода, подредени от най-безопасния към най-грубия.
Първо: кажи на услугата да преотвори логовете си. Това е правилният начин, защото процесът сам затваря стария descriptor и създава нов файл:
Code:
nginx -s reopen
systemctl reload apache2
systemctl kill -s HUP rsyslog
Старият файлов обект остава без референции и файловата система освобождава блоковете.
Второ: нулирай съдържанието през самия descriptor. Ако процесът няма механизъм за reopen:
Code:
truncate -s 0 /proc/1842/fd/5
Мястото се връща веднага, а процесът продължава да работи, без да забележи нищо.
Тук има уговорка, която си струва да се знае предварително. Ако файлът е отворен с O_APPEND, всичко е чисто. Ако не е, процесът пази собствен offset и следващият запис ще се случи на старата позиция, при което файлът става sparse и ls -l отново показва огромен размер. Заетите блокове остават малко, но гледката е объркваща.
Трето: рестарт на процеса. Работи винаги, но е най-скъпото решение и точно затова е последно.
И една дума за причината. Ако това се повтаря периодично, проблемът обикновено не е в rm, а в logrotate конфигурация, при която услугата не получава сигнал да преотвори файловете си. В такива случаи copytruncate е компромисът, който спасява ситуацията, с цената на малък прозорец, в който могат да се загубят редове.
Hard links: две имена, един файлов обект
Hard links са най-чистият начин да се види защо pathname и файл не са едно и също:
Code:
echo "DTGarage" > file1
ln file1 file2
ls -li file1 file2
Резултат:
Code:
527881 -rw-r--r-- 2 toni users 9 file1
527881 -rw-r--r-- 2 toni users 9 file2
И двете имена сочат към един и същ inode. Сега:
Code:
rm file1
cat file2
DTGarage
Нищо не е възкръснало и нищо не е възстановено. Премахнато е едното име, второто продължава да сочи към същия файлов обект.
Ако link count е по-голям от единица, а не знаеш къде е другото име, търсиш по inode номер:
Code:
find /mount/point -xdev -inum 527881
Търсенето е в рамките на една файлова система, защото inode номерата не са уникални между отделните filesystems. Точно затова -xdev тук не е излишна предпазливост.
Метафората е проста: имаш две карти към едно и също място. Изгарянето на едната карта не премахва терена.
mmap: референция може да остане и без отворен descriptor
Файл може да бъде memory-mapped чрез mmap(). След успешното създаване на mapping-а първоначалният file descriptor може да бъде затворен, без mapping-ът да изчезне.
Затова твърдението:
Code:
няма отворени file descriptors
=======================
файлът задължително е освободен
не е достатъчно точно. По-добрият модел е:
Code:
няма directory links
+
няма активни references
=======================
файловият обект може да бъде освободен
При обикновен огромен лог файл най-често ще срещнеш open descriptor, не сложен memory mapping. Но ако ще обясняваме механизма сериозно, тази уговорка трябва да я има.
Точността не е да сложиш повече термини. Точността е да не превърнеш най-честия случай в универсален закон.
df срещу du: двама свидетели, два различни въпроса
Друг класически симптом:
Code:
du: 40 GB използвани
df: 90 GB използвани
На пръв поглед единият инструмент трябва да греши. Всъщност двата отговарят на различни въпроси. du обхожда файловата йерархия и смята файловете, които може да достигне през directory tree. df пита файловата система за нейното собствено счетоводство на блоковете.
При голям unlink-нат файл, който още се държи от процес:
Code:
du -> pathname вече липсва от дървото
df -> блоковете още не са освободени
Точно затова при съществено разминаване една от първите проверки е lsof +L1.
Но тук е важно да не превръщаме полезния симптом в диагноза. Разлика между df и du не означава автоматично open-deleted file. Други причини могат да бъдат snapshots, reserved blocks, различни mount points, filesystem metadata, bind mounts, storage abstraction и network filesystems.
Добрата диагностика не казва "виждал съм това преди". Тя казва "това е първата хипотеза, която ще проверя".
Snapshots: текущото състояние не е цялата история
Тук думата "изтрит" става още по-опасна. След:
Code:
rm -rf important/
директорията вече не съществува в текущия filesystem namespace. Но ако системата използва snapshot технология - Btrfs, ZFS, LVM, storage-level или VM snapshots, backup системи - старото състояние може да продължи да пази същите данни.
Затова изречението "файлът го няма" не е технически завършено. Правилният въпрос е:
Няма го на кой слой?
Може да го няма в текущата директория, но да го има в snapshot. Може да го няма във filesystem-а, но да съществува в backup. Може да е премахнат логически, но storage устройството още да не е презаписало физическите клетки.
Изтриването на настоящето не е автоматично изтриване на историята.
rm -rf не е secure erase
Това е митът, който си струва да се счупи внимателно.
При rm -rf directory/ инструментът рекурсивно обхожда съдържанието и премахва съответните entries и директории според правилата на файловата система:
Code:
directory/
|
+-- file1 -> remove
|
+-- file2 -> remove
|
+-- subdir/
|
+-- file3 -> remove
-r означава рекурсивна обработка. -f означава force според поведението на rm. Тези флагове правят командата по-опасна заради мащаба и липсата на част от интерактивните проверки. Но нито един от двата не превръща инструмента в механизъм за физическа санитизация на storage.
Тоест rm -rf secret/ не означава:
Code:
физически презапиши всеки байт,
който някога е принадлежал на тези файлове
Тук трябва да държим разделени три състояния:
- Pathname вече не съществува.
- Файловата система вече може да счита ресурсите за свободни или освобождаеми.
- Старото физическо съдържание вече не съществува или не може да бъде възстановено от носителя.
Това не са синоними. И точно между тях живее по-голямата част от объркването около "сигурното изтриване".
HDD: unlink не означава overwrite
При класически магнитен диск unlink операцията сама по себе си не е команда за презаписване на секторите. Файловата система може да маркира съответното пространство като свободно за бъдеща употреба, докато старото физическо съдържание остане там до последващо презаписване:
Code:
unlink != physical overwrite
Това не означава, че recovery винаги ще е възможно. Означава само, че логическото освобождаване на файлово пространство и физическото унищожаване на старите магнитни данни са различни операции.
SSD, TRIM и контролерът между теб и NAND
При SSD картината е по-сложна. Между логическия block address, който операционната система вижда, и реалната NAND клетка стои контролер с Flash Translation Layer, а към това се добавят wear leveling, garbage collection, over-provisioning и TRIM/discard.
Операционната система може да каже на устройството, че дадени logical blocks вече не съдържат полезни данни. Оттам нататък физическото управление на NAND е работа на контролера и неговия firmware.
Затова и двете крайни твърдения са лоши:
Code:
TRIM значи, че старите данни физически изчезват веднага.
или
След rm данните със сигурност още седят върху NAND.
И двете се опитват да опишат физическо поведение, което потребителската операционна система не контролира толкова директно.
Практическият резултат е, че след TRIM recovery на SSD често става много по-трудно или невъзможно, но причината не е, че rm внезапно се е превърнал в shredder.
shred: полезен инструмент, но не универсална присъда
Командата shred secret.txt е създадена с идеята съдържанието да бъде презаписано. Проблемът е, че тази стратегия предполага нещо важно: логическото презаписване трябва действително да стигне до същото физическо място, където се е намирало предишното съдържание.
Това предположение може да бъде нарушено от SSD wear leveling, Copy-on-Write файлови системи, snapshots, RAID и други storage layers, виртуални block devices и remote storage.
Затова shred не е универсален бутон "унищожи завинаги". За чувствителни данни по-зрелият модел е да се мисли за криптиране още от момента на записване, crypto erase, sanitize възможностите на устройството и политиката за целия носител.
Понякога най-добрият начин да унищожиш една тайна е тя никога да не е лежала некриптирана върху диска.
Изтрих го, а след секунда пак беше там
Това е друг клас проблем и не трябва да се смесва с open-deleted файловете.
Code:
rm /run/application.pid
ls /run/application.pid
Файлът отново съществува. Първата реакция често е "не се е изтрил". Но много по-вероятният сценарий е:
Code:
rm го е премахнал успешно
|
v
процес или услуга го е създала отново
Типични кандидати са systemd services и systemd-tmpfiles, container workloads, cron jobs, watchdog процеси, logrotate и application supervisors.
От гледна точка на човек, който гледа само ls, двата сценария изглеждат еднакво. От гледна точка на системната диагностика са коренно различни. Едното е неуспешно премахване. Другото е успешно премахване, последвано от ново създаване.
За базово следене на директория:
Code:
inotifywait -m /path/to/directory
Виждаш събития от типа CREATE, MODIFY, DELETE, MOVED_TO. При по-сериозен случай вече идва Linux auditing инфраструктурата и въпросът не е "защо файлът пак е тук", а "кой процес го създаде, кога и под кой потребител".
Това е разликата между наблюдение и диагностика.
Operation not permitted: когато unlink изобщо не се случва
Трети различен сценарий. rm file връща:
Code:
Operation not permitted
Кандидатите са няколко: read-only mount, права върху родителската директория, sticky bit, security module като SELinux или AppArmor, ограничени capabilities в container и immutable attribute.
Последният се проверява с lsattr:
Code:
lsattr file
----i--------- file
и при съответните привилегии се маха с chattr -i. Не всички файлови системи поддържат всички attributes, така че този флаг не е универсално обяснение.
Най-важното: този случай няма нищо общо с open-deleted file. Тук unlink операцията не е станала. Файлът не е "изтрит, но жив". Той просто не е бил премахнат.
GNU rm има предпазители - но не може да чете намерения
Също толкова неточно е да се разказва митът, че всяко rm -rf / на модерна GNU/Linux система непременно ще започне безусловно да унищожава root filesystem-а. GNU rm има защита --preserve-root, която по подразбиране блокира някои очевидно катастрофални recursive операции върху root.
Само че тази защита гледа аргумента, който ѝ е подаден. А shell-ът разгъва wildcards преди rm изобщо да бъде стартиран. Затова:
Code:
rm -rf /*
никога не стига до rm като "/". До програмата пристига готов списък от /bin, /etc, /home и всичко останало, като напълно валидни отделни пътища. Същото важи за /srv/project/, /home/toni/data/ или /mnt/production/.
Ако си посочил грешния път и имаш права да го премахнеш, системата няма как да разбере, че ръката ти е изпреварила мозъка.
Linux проверява аргументи и права. Не проверява намерения.
Това е причината rm -rf да заслужава уважение. Не защото командата е зла, а защото е безразлично точна.
Полигонът: направи си файл-призрак
Тази част е безопасна за упражнение в /tmp. В първи terminal:
Code:
echo "DTGarage forensic test" > /tmp/ghost.txt
tail -f /tmp/ghost.txt
tail държи файла отворен. Във втори terminal:
Code:
rm /tmp/ghost.txt
ls -l /tmp/ghost.txt
No such file or directory
После:
Code:
lsof +L1
tail ... /tmp/ghost.txt (deleted)
Това е целият урок в един експеримент. Pathname вече го няма. Процесът обаче държи open file reference.
Сега спри tail с Ctrl+C. Descriptor-ът се затваря и ако няма други активни референции, файловата система вече може да освободи файловия обект и свързаните с него ресурси.
Няма магия. Има жизнен цикъл.
Практическа диагностика
Когато след rm нещо не изглежда логично, не започвай с предположение. Раздели проблема по симптом.
- Изтрих голям файл, но дисковото пространство не се освободи: провери df -h и lsof +L1.
- Файлът е с (deleted), но процесът работи: разгледай /proc/<PID>/fd/.
- Трябва ти мястото веднага: reopen през самата услуга, truncate -s 0 /proc/<PID>/fd/<FD> или, в краен случай, рестарт.
- Искаш да спасиш съдържание от unlink-нат regular file: провери дали descriptor-ът в /proc/<PID>/fd/<FD> все още дава достъп.
- Файлът постоянно се появява отново: търси кой го създава, вместо да обвиняваш rm.
- Получаваш Operation not permitted: провери mount state, права, security module и при подходяща файлова система lsattr.
- Има друго име към същия обект: ls -li и find /mount/point -xdev -inum <номер>.
- df и du се разминават: open-deleted files са една хипотеза, не единствената.
- Става дума за чувствителни данни: не използвай rm като синоним на secure erase.
Честната част: какво не доказва тази тема
Тук е важно да не направим обратната грешка и да превърнем нюанса в нов мит.
Фактът, че rm не е secure erase, не означава, че всеки изтрит файл може да бъде възстановен.
Recovery зависи от файловата система, последващите записи, TRIM/discard, storage контролера, snapshots, криптирането и още куп конкретни фактори.
Също така lsof +L1 не обяснява всяко разминаване между df и du. /proc/<PID>/fd не е универсален recovery механизъм. А наличието на snapshot не означава автоматично, че той съдържа точно файла, който търсиш.
Целта не е да заменим едно опростяване с друго. Целта е да знаем на кой слой се намираме и какво точно доказва наблюдението ни.
Последният ред
Слабият модел е име = файл. По-полезният разделя слоевете и помни, че процесът стига до същия обект и по друг път:
Code:
PATHNAME -> DIRECTORY ENTRY -> FILESYSTEM OBJECT
PROCESS -> FILE DESCRIPTOR -> FILESYSTEM OBJECT
PROCESS -> MEMORY MAPPING -> FILESYSTEM OBJECT
Затова изчезналият pathname не означава задължително изчезнал filesystem object. А освободеният filesystem object не означава изтрита физическа история на всеки storage слой.
След rm един pathname може да е изчезнал, докато процес продължава да използва обекта. Файловата система може да е освободила името, докато snapshot пази стара версия. SSD може да е получил discard информация, без операционната система да има директна видимост върху съдбата на всяка NAND клетка.
Затова най-добрият въпрос не е:
"Изтрит ли е файлът?"
А:
"На кой слой вече не съществува?"
Тази разлика изглежда дребна, докато не започнеш да администрираш истински системи. После става основна.
Linux рядко нарушава собствената си логика. По-често ние гледаме един слой и правим заключение за всички останали.
Ключови думи: linux rm, rm -rf, unlink, inode, hard link, open deleted file, lsof +L1, /proc fd, truncate, df du difference, linux filesystem, ext4, xfs, btrfs, linux recovery, SSD TRIM, secure erase, shred, snapshots, logrotate, linux administration
Източници: Linux man-pages project - unlink(2), mmap(2), open(2), proc_pid_fd(5); lsof(8); logrotate(8); GNU Coreutils документация за rm, shred и truncate; документация на Linux kernel и файловите системи за discard/TRIM; документация на съответните файлови системи за snapshots и Copy-on-Write. Командите и примерите са Linux/Unix ориентирани; конкретното поведение на storage слоя зависи от файловата система, ядрото, носителя и контролера.
toni@dtgarage:~$ whoami
Тони Ангелчовски | Ексклузивно за DTGaraGe
toni@dtgarage:~$ cat LICENSE
Копирането и препубликуването без разрешение не е позволено.
toni@dtgarage:~$ ./support.sh
Подкрепи DTGaraGe и независимото техническо съдържание
toni@dtgarage:~$ █
Тони Ангелчовски | Ексклузивно за DTGaraGe
toni@dtgarage:~$ cat LICENSE
toni@dtgarage:~$ ./support.sh
toni@dtgarage:~$ █
Last edited: